diff --git a/.gitignore b/.gitignore index aa31b579..6e3cd53c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ *.log *.pdf *.dvi +*.json +*.fmt flamegraph.svg perf.data* callgrind* diff --git a/Cargo.lock b/Cargo.lock index 6d248cef..a60053b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + [[package]] name = "arrayref" version = "0.3.7" @@ -60,9 +66,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded" [[package]] name = "blake2b_simd" @@ -124,45 +130,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags 1.3.2", - "textwrap 0.11.0", + "textwrap", "unicode-width", ] [[package]] name = "clap" -version = "3.2.25" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" dependencies = [ - "bitflags 1.3.2", + "clap_builder", "clap_derive", - "clap_lex", - "indexmap", "once_cell", - "textwrap 0.16.0", +] + +[[package]] +name = "clap_builder" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +dependencies = [ + "anstyle", + "bitflags 1.3.2", + "clap_lex", ] [[package]] name = "clap_derive" -version = "3.2.25" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colored" @@ -256,9 +266,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", @@ -269,9 +279,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -368,12 +378,6 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "heck" version = "0.4.1" @@ -421,16 +425,6 @@ dependencies = [ "cc", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown", -] - [[package]] name = "itertools" version = "0.10.5" @@ -448,9 +442,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -480,9 +474,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "memchr" @@ -492,9 +486,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -511,7 +505,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c624fa1b7aab6bd2aff6e9b18565cc0363b6d45cbcd7465c9ed5e3740ebf097" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.2", "libc", "nix", "smallstr", @@ -574,12 +568,6 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" -[[package]] -name = "os_str_bytes" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" - [[package]] name = "paste" version = "1.0.12" @@ -668,35 +656,11 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -871,9 +835,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] @@ -890,13 +854,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn", ] [[package]] @@ -937,17 +901,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.18" @@ -976,10 +929,12 @@ dependencies = [ name = "texcraft" version = "0.1.0" dependencies = [ - "clap 3.2.25", + "clap 4.3.4", "colored", "performance", "rand", + "rmp-serde", + "serde_json", "texlang-core", "texlang-stdlib", ] @@ -1038,12 +993,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thiserror" version = "1.0.40" @@ -1061,7 +1010,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn", ] [[package]] @@ -1110,12 +1059,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "walkdir" version = "2.3.3" @@ -1140,9 +1083,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1150,24 +1093,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.18", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1175,28 +1118,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/bin/Cargo.toml b/bin/Cargo.toml index a3b0e1b5..00be462e 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -12,10 +12,12 @@ name = "randtex" path = "src/randtex.rs" [dependencies] -clap = {version="3.0.0-beta.5", default-features=false, features=["std", "derive"]} +clap = {version="4.3.3", default-features=false, features=["std", "derive"]} colored = "2" rand = "0.8.4" +rmp-serde = {version="^1.0"} +serde_json = {version="1.0"} texlang-core = { path = "../crates/texlang-core" } -texlang-stdlib = { path = "../crates/texlang-stdlib", features = ["repl"]} +texlang-stdlib = { path = "../crates/texlang-stdlib", features = ["repl", "serde"]} performance = {path = "../performance"} diff --git a/bin/src/texcraft.rs b/bin/src/texcraft.rs index a5522a33..1603e944 100644 --- a/bin/src/texcraft.rs +++ b/bin/src/texcraft.rs @@ -1,9 +1,10 @@ use clap::Parser; use colored::Colorize; -use std::collections::HashMap; +use std::ffi::OsStr; use std::fs; use std::path::PathBuf; use texlang_core::*; +use texlang_stdlib::job; use texlang_stdlib::repl; use texlang_stdlib::script; use texlang_stdlib::StdLibState; @@ -16,6 +17,10 @@ use texlang_stdlib::StdLibState; #[derive(Parser)] #[clap(version)] struct Cli { + /// Path to a format file + #[arg(long)] + format_file: Option, + #[clap(subcommand)] sub_command: SubCommand, } @@ -44,14 +49,16 @@ struct Run { fn main() { let args: Cli = Cli::parse(); + let is_repl = matches!(args.sub_command, SubCommand::Repl); + let mut vm = new_vm(args.format_file, is_repl); let result = match args.sub_command { - SubCommand::Doc(d) => doc(d.name), + SubCommand::Doc(d) => doc(&vm, d.name), SubCommand::Repl => { - repl(); + repl(&mut vm); Ok(()) } SubCommand::Run(run_args) => { - let result = run(run_args.file_path); + let result = run(&mut vm, run_args.file_path); if let Err(err) = result { println!["{err}"]; std::process::exit(1); @@ -65,10 +72,11 @@ fn main() { } } -fn run(mut path: PathBuf) -> Result<(), Box> { +fn run(vm: &mut vm::VM, mut path: PathBuf) -> Result<(), Box> { if path.extension().is_none() { path.set_extension("tex"); } + vm.state.job.set_job_name(&path); let source_code = match fs::read_to_string(&path) { Ok(source_code) => source_code, Err(err) => { @@ -76,19 +84,17 @@ fn run(mut path: PathBuf) -> Result<(), Box> { std::process::exit(1); } }; - let mut vm = new_vm(); // The only error possible is input stack size exceeded, which can't be hit. // TODO: the external VM method shouldn't error let _ = vm.push_source(path.to_string_lossy().to_string(), source_code); - script::run_and_write(&mut vm, Box::new(std::io::stdout()))?; + script::run_and_write(vm, Box::new(std::io::stdout()))?; Ok(()) } -fn repl() { +fn repl(vm: &mut vm::VM) { println!("{}\n", REPL_START.trim()); - let mut vm = new_repl_vm(); repl::run::run( - &mut vm, + vm, repl::RunOptions { prompt: "tex> ", help: REPL_HELP, @@ -125,9 +131,7 @@ Tips Website: https://texcraft.dev "; -fn doc(cs_name: Option) -> Result<(), String> { - let vm = new_vm(); - +fn doc(vm: &vm::VM, cs_name: Option) -> Result<(), String> { match cs_name { None => { let mut cs_names = Vec::new(); @@ -175,21 +179,7 @@ fn doc(cs_name: Option) -> Result<(), String> { } } -fn new_vm() -> Box> { - vm::VM::::new(initial_built_ins()) -} - -fn new_repl_vm() -> Box> { - let mut m = initial_built_ins(); - m.insert("doc", repl::get_doc()); - m.insert("help", repl::get_help()); - m.insert("exit", repl::get_exit()); - m.insert("quit", repl::get_exit()); - m.insert("q", repl::get_exit()); - vm::VM::::new(m) -} - -fn initial_built_ins() -> HashMap<&'static str, command::BuiltIn> { +fn new_vm(format_file_path: Option, repl: bool) -> Box> { let mut m = StdLibState::all_initial_built_ins(); m.insert("par", script::get_par()); m.insert("newline", script::get_newline()); @@ -197,5 +187,27 @@ fn initial_built_ins() -> HashMap<&'static str, command::BuiltIn> { "\\", command::Command::Character(token::Value::Other('\\')).into(), ); - m + if repl { + m.insert("doc", repl::get_doc()); + m.insert("help", repl::get_help()); + m.insert("exit", repl::get_exit()); + m.insert("quit", repl::get_exit()); + m.insert("q", repl::get_exit()); + } else { + m.insert("dump", job::get_dump()); + } + + match format_file_path { + None => vm::VM::::new(m), + Some(path) => { + let format_file = std::fs::read(&path).unwrap(); + if path.extension().map(OsStr::to_str) == Some(Some("json")) { + let mut deserializer = serde_json::Deserializer::from_slice(&format_file); + vm::VM::deserialize(&mut deserializer, m) + } else { + let mut deserializer = rmp_serde::decode::Deserializer::from_read_ref(&format_file); + vm::VM::deserialize(&mut deserializer, m) + } + } + } } diff --git a/crates/texcraft-stdext/src/serde_tools.rs b/crates/texcraft-stdext/src/serde_tools.rs index ec88ae0e..f9c07d81 100644 --- a/crates/texcraft-stdext/src/serde_tools.rs +++ b/crates/texcraft-stdext/src/serde_tools.rs @@ -30,3 +30,28 @@ where let a: [T; N] = v.try_into().unwrap(); Ok(a) } + +/// Function that serializes strings +/// +/// This is intended for use with serde derive's +/// `#[serde(serialize_with = "path")]` field attribute. +pub fn serialize_str, S>(input: T, serializer: S) -> Result +where + S: serde::Serializer, +{ + let s: &str = input.as_ref(); + s.serialize(serializer) +} + +/// Function that deserializes reference counted constant strings +/// +/// This is intended for use with serde derive's +/// `#[serde(deserialize_with = "path")]` field attribute. +pub fn deserialize_rc_str<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + // TODO: should probably use Cow<> here to support not coping the string + let s = String::deserialize(deserializer)?; + Ok(s.into()) +} diff --git a/crates/texcraft-stdext/src/str.rs b/crates/texcraft-stdext/src/str.rs index 695b6016..ffa0afe7 100644 --- a/crates/texcraft-stdext/src/str.rs +++ b/crates/texcraft-stdext/src/str.rs @@ -2,7 +2,17 @@ /// /// This iterator is an alternative to the standard library's [Chars](std::str::Chars). /// This iterator has shared ownership of the string being iterated over and avoids lifetime issues. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct OwningChars { + // TODO: we don't need to serialize the full string, we just nneed to serialize + // everything after pos. + #[cfg_attr( + feature = "serde", + serde( + serialize_with = "crate::serde_tools::serialize_str", + deserialize_with = "crate::serde_tools::deserialize_rc_str" + ) + )] s: std::rc::Rc, pos: usize, } diff --git a/crates/texlang-core/src/command/map.rs b/crates/texlang-core/src/command/map.rs index 2db8e2c4..c16fd4a5 100644 --- a/crates/texlang-core/src/command/map.rs +++ b/crates/texlang-core/src/command/map.rs @@ -81,6 +81,10 @@ impl Map { } } + pub fn initial_built_ins(&self) -> &HashMap> { + &self.initial_built_ins + } + pub fn get_command_slow(&self, name: &token::CsName) -> Option> { let command = match self.get_command(name) { None => return None, @@ -318,6 +322,7 @@ impl<'a> SerializableMap<'a> { pub(crate) fn finish_deserialization( self, initial_built_ins: HashMap>, + interner: &token::CsNameInterner, ) -> Map { let macros: Vec> = self .macros @@ -332,7 +337,15 @@ impl<'a> SerializableMap<'a> { |(cs_name, serialized_command): (token::CsName, &SerializableCommand)| { let command = match serialized_command { SerializableCommand::BuiltIn(cs_name) => { - initial_built_ins.get(cs_name).unwrap().cmd.clone() + match initial_built_ins.get(cs_name) { + None => { + panic!( + "unknown control sequence {:?}", + interner.resolve(*cs_name) + ) + } + Some(cmd) => cmd.cmd.clone(), + } } SerializableCommand::VariableArrayStatic(cs_name, index) => { match &initial_built_ins.get(cs_name).unwrap().cmd { diff --git a/crates/texlang-core/src/error/display.rs b/crates/texlang-core/src/error/display.rs index b22b2c9e..15850a92 100644 --- a/crates/texlang-core/src/error/display.rs +++ b/crates/texlang-core/src/error/display.rs @@ -7,7 +7,7 @@ pub fn format_error(f: &mut std::fmt::Formatter<'_>, err: &error::Error) -> std: let (error_line, immediate_command) = match root.kind() { error::Kind::Token(s) => (s, stack.last()), error::Kind::EndOfInput(s) => (s, stack.last()), - error::Kind::InvalidCodePath => (&stack.last().unwrap().trace, None), + error::Kind::FailedPrecondition => (&stack.last().unwrap().trace, None), }; let line = PrimaryLine { @@ -211,7 +211,7 @@ fn fmt_source_code_trace( .with_content(format!( "{} {}:{}:{}", ">>>".bright_cyan().bold(), - s.file_name, + s.file_name.display(), s.line_number, s.index + 1 )) @@ -256,7 +256,7 @@ fn fmt_source_code_trace_light( let prefix = format!( "{}{}:{}:{}", " ".repeat(indent), - s.file_name, + s.file_name.display(), s.line_number, s.index + 1 ); diff --git a/crates/texlang-core/src/error/mod.rs b/crates/texlang-core/src/error/mod.rs index b4036862..78205f4e 100644 --- a/crates/texlang-core/src/error/mod.rs +++ b/crates/texlang-core/src/error/mod.rs @@ -69,7 +69,7 @@ pub struct PropagatedError { pub enum Kind<'a> { Token(&'a trace::SourceCodeTrace), EndOfInput(&'a trace::SourceCodeTrace), - InvalidCodePath, + FailedPrecondition, } pub trait TexError: std::fmt::Debug { @@ -97,7 +97,9 @@ pub trait TexError: std::fmt::Debug { } } Kind::EndOfInput(_) => "input ended here".into(), - Kind::InvalidCodePath => todo!(), + Kind::FailedPrecondition => { + "failed precondition error while running this command".into() + } } } } @@ -198,6 +200,45 @@ impl TexError for SimpleEndOfInputError { } } +#[derive(Debug)] +pub struct SimpleFailedPreconditionError { + pub title: String, + pub text_notes: Vec, +} + +impl SimpleFailedPreconditionError { + /// Create a new simple failed precondition error. + pub fn new>(title: T) -> Self { + Self { + title: title.as_ref().into(), + text_notes: vec![], + } + } + + pub fn with_note>(mut self, note: T) -> Self { + self.text_notes.push(note.into()); + self + } +} + +impl TexError for SimpleFailedPreconditionError { + fn kind(&self) -> Kind { + Kind::FailedPrecondition + } + + fn title(&self) -> String { + self.title.clone() + } + + fn notes(&self) -> Vec { + let mut notes = vec![]; + for text_note in &self.text_notes { + notes.push(text_note.into()) + } + notes + } +} + #[derive(Debug)] pub struct UndefinedCommandError { pub trace: trace::SourceCodeTrace, diff --git a/crates/texlang-core/src/token/lexer.rs b/crates/texlang-core/src/token/lexer.rs index 163c310d..e4694432 100644 --- a/crates/texlang-core/src/token/lexer.rs +++ b/crates/texlang-core/src/token/lexer.rs @@ -38,10 +38,12 @@ impl CatCodeFn for std::collections::HashMap { } /// The Texlang lexer +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Lexer { raw_lexer: RawLexer, trim_next_whitespace: bool, // We read control sequence names into a shared buffer to avoid allocating for each one. + #[cfg_attr(feature = "serde", serde(skip))] buffer: String, } @@ -170,6 +172,7 @@ struct RawToken { trace_key: trace::Key, } +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] struct RawLexer { iter: OwningChars, trace_key_range: trace::KeyRange, diff --git a/crates/texlang-core/src/token/mod.rs b/crates/texlang-core/src/token/mod.rs index b88a92c9..71c6ec35 100644 --- a/crates/texlang-core/src/token/mod.rs +++ b/crates/texlang-core/src/token/mod.rs @@ -146,6 +146,7 @@ impl Token { self.value } + #[inline] pub fn trace_key(&self) -> trace::Key { self.trace_key } diff --git a/crates/texlang-core/src/token/trace.rs b/crates/texlang-core/src/token/trace.rs index 8b23297c..25e4ffb1 100644 --- a/crates/texlang-core/src/token/trace.rs +++ b/crates/texlang-core/src/token/trace.rs @@ -36,6 +36,7 @@ use crate::token::{CsNameInterner, Token, Value}; use std::collections::BTreeMap; use std::ops::Bound::Included; +use std::path::PathBuf; /// Key attached to tokens to enable tracing them. /// @@ -51,6 +52,7 @@ impl Key { } /// Range of free keys that may be assigned to tokens. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct KeyRange { next: u32, limit: u32, @@ -99,7 +101,7 @@ impl KeyRange { #[derive(Debug, PartialEq, Eq)] pub struct SourceCodeTrace { /// Name of the file this token came from. - pub file_name: String, + pub file_name: PathBuf, /// Content of the line this token came from. pub line_content: String, /// Number of the line within the file, starting at 1. @@ -115,6 +117,7 @@ pub struct SourceCodeTrace { /// Data structure that records information for token tracing #[derive(Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Tracer { checkpoints: BTreeMap, next_key: u32, @@ -135,7 +138,7 @@ impl Tracer { pub fn register_source_code( &mut self, token: Option, - file_name: String, + file_name: PathBuf, source_code: &str, ) -> KeyRange { let len = match u32::try_from(source_code.len()) { @@ -250,8 +253,9 @@ impl Tracer { } } +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] enum Checkpoint { - SourceCode { file_name: String, content: String }, + SourceCode { file_name: PathBuf, content: String }, } #[cfg(test)] @@ -260,7 +264,7 @@ mod tests { #[test] fn one_source_code() { - let file_name = "input.tex".to_string(); + let file_name: PathBuf = "input.tex".into(); let line_1 = "hël".to_string(); let line_2 = "wor\\cömmand".to_string(); let line_3 = "hël".to_string(); @@ -403,17 +407,17 @@ mod tests { let mut tracer: Tracer = Default::default(); let interner: CsNameInterner = Default::default(); - let file_1 = "a.tex".to_string(); + let file_1: PathBuf = "a.tex".into(); let file_1_content = "a".to_string(); let mut range = tracer.register_source_code(None, file_1.clone(), &file_1_content); tokens.push(Token::new_letter('a', range.next())); - let file_2 = "b.tex".to_string(); + let file_2: PathBuf = "b.tex".into(); let file_2_content = "b".to_string(); let mut range = tracer.register_source_code(None, file_2.clone(), &file_2_content); tokens.push(Token::new_letter('b', range.next())); - let file_3 = "c.tex".to_string(); + let file_3: PathBuf = "c.tex".into(); let file_3_content = "c".to_string(); let mut range = tracer.register_source_code(None, file_3.clone(), &file_3_content); tokens.push(Token::new_letter('c', range.next())); diff --git a/crates/texlang-core/src/variable.rs b/crates/texlang-core/src/variable.rs index d9d715aa..01251517 100644 --- a/crates/texlang-core/src/variable.rs +++ b/crates/texlang-core/src/variable.rs @@ -384,7 +384,7 @@ impl Command { } /// Create a new getter provider. - /// + /// /// A getter provider is a variable command that is not intended to be invoked directly /// In fact, the variable command will panic the program if it is invoked. /// Instead the provider is included in a VM's initial commands so that @@ -396,7 +396,11 @@ impl Command { ref_fn: RefFn, ref_mut_fn: MutRefFn, ) -> Command { - SupportedType::new_command(ref_fn, ref_mut_fn, Some(IndexResolver::Dynamic(|_, _|{ panic!() }))) + SupportedType::new_command( + ref_fn, + ref_mut_fn, + Some(IndexResolver::Dynamic(|_, _| panic!())), + ) } /// Create a new variable using the provided getters. diff --git a/crates/texlang-core/src/vm/mod.rs b/crates/texlang-core/src/vm/mod.rs index 2f2e10e8..60c6ac31 100644 --- a/crates/texlang-core/src/vm/mod.rs +++ b/crates/texlang-core/src/vm/mod.rs @@ -20,6 +20,7 @@ use crate::token::Value; use crate::variable; use std::cell::RefCell; use std::collections::HashMap; +use std::path::PathBuf; use std::rc::Rc; use texcraft_stdext::collections::groupingmap; @@ -221,6 +222,11 @@ pub trait FileSystem { /// /// This is implemented by [std::fs::read_to_string]. fn read_to_string(&self, path: &std::path::Path) -> std::io::Result; + + /// Write a slice of bytes to a file. + /// + /// This is implemented by [std::fs::write]. + fn write_bytes(&self, path: &std::path::Path, contents: &[u8]) -> std::io::Result<()>; } struct RealFileSystem; @@ -229,6 +235,9 @@ impl FileSystem for RealFileSystem { fn read_to_string(&self, path: &std::path::Path) -> std::io::Result { std::fs::read_to_string(path) } + fn write_bytes(&self, path: &std::path::Path, contents: &[u8]) -> std::io::Result<()> { + std::fs::write(path, contents) + } } /// Implementations of this trait may be used as the state in a Texlang VM. @@ -354,7 +363,7 @@ impl VM { /// /// TeX input source code is organized as a stack. /// Pushing source code onto the stack will mean it is executed first. - pub fn push_source, T2: Into>( + pub fn push_source, T2: Into>( &mut self, file_name: T1, source_code: T2, @@ -426,6 +435,12 @@ impl VM { } /// Parts of the VM that are private. +// We have serde(bound="") because otherwise serde tries to put a `Default` bound on S. +#[cfg_attr( + feature = "serde", + derive(::serde::Serialize, ::serde::Deserialize), + serde(bound = "") +)] struct Internal { // The sources form a stack. We store the top element directly on the VM // for performance reasons. @@ -436,8 +451,11 @@ struct Internal { tracer: trace::Tracer, - token_buffers: std::collections::BinaryHeap, // add a #[skip] + #[cfg_attr(feature = "serde", serde(skip))] + token_buffers: std::collections::BinaryHeap, + // Groups are handled manually in (de)serialization because we need to have special logic that uses the command map + #[cfg_attr(feature = "serde", serde(skip))] groups: Vec>, } @@ -457,7 +475,7 @@ impl Internal { fn push_source( &mut self, token: Option, - file_name: String, + file_name: PathBuf, source_code: String, ) -> Result<(), Box> { let source_code: std::rc::Rc = source_code.into(); @@ -505,6 +523,7 @@ impl Internal { } } +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] struct Source { expansions: Vec, root: lexer::Lexer, diff --git a/crates/texlang-core/src/vm/serde.rs b/crates/texlang-core/src/vm/serde.rs index faa8e934..abab2fc0 100644 --- a/crates/texlang-core/src/vm/serde.rs +++ b/crates/texlang-core/src/vm/serde.rs @@ -8,7 +8,7 @@ use std::rc::Rc; struct SerializableVM<'a, S> { state: &'a S, commands_map: &'a command::Map, - interner: &'a token::CsNameInterner, + internal: &'a vm::Internal, } impl<'a, S> SerializableVM<'a, S> { @@ -16,7 +16,7 @@ impl<'a, S> SerializableVM<'a, S> { Self { state: &vm.state, commands_map: &vm.commands_map, - interner: &vm.internal.cs_name_interner, + internal: &vm.internal, } } } @@ -35,26 +35,26 @@ impl Serialize for vm::VM { struct DeserializedVM<'a, S> { state: S, commands_map: command::map::SerializableMap<'a>, - interner: token::CsNameInterner, + internal: vm::Internal, } fn finish_deserialization( - #[allow(clippy::boxed_local)] deserialized: Box>, + #[allow(clippy::boxed_local)] mut deserialized: Box>, initial_built_ins: HashMap<&str, command::BuiltIn>, ) -> Box> { - let mut internal = vm::Internal::new(deserialized.interner); let initial_built_ins = initial_built_ins .into_iter() .map(|(key, value)| { - let cs_name = internal.cs_name_interner.get_or_intern(key); + let cs_name = deserialized.internal.cs_name_interner.get_or_intern(key); (cs_name, value) }) .collect(); + let commands_map = deserialized + .commands_map + .finish_deserialization(initial_built_ins, &deserialized.internal.cs_name_interner); Box::new(vm::VM { state: deserialized.state, - commands_map: deserialized - .commands_map - .finish_deserialization(initial_built_ins), + commands_map, file_system: Box::new(super::RealFileSystem {}), terminal: Rc::new(RefCell::new(std::io::stderr())), log_file: Rc::new(RefCell::new(std::io::sink())), @@ -65,7 +65,7 @@ fn finish_deserialization( None } }, - internal, + internal: deserialized.internal, }) } diff --git a/crates/texlang-core/src/vm/streams.rs b/crates/texlang-core/src/vm/streams.rs index bf99f429..0debbb02 100644 --- a/crates/texlang-core/src/vm/streams.rs +++ b/crates/texlang-core/src/vm/streams.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use super::TexlangState; use crate::token::trace; use crate::token::Token; @@ -224,13 +226,14 @@ impl ExpansionInput { unsafe { &mut *(vm as *mut vm::VM as *mut ExpansionInput) } } } + impl ExpansionInput { /// Push source code to the front of the input stream. #[inline] pub fn push_source( &mut self, token: Token, - file_name: String, + file_name: PathBuf, source_code: String, ) -> Result<(), Box> { self.0 @@ -239,7 +242,19 @@ impl ExpansionInput { .internal .push_source(Some(token), file_name, source_code) } + + pub fn push_string_tokens(&mut self, token: Token, s: &str) { + let trace_key = token.trace_key(); + for c in s.chars().rev() { + let token = match c { + ' ' => token::Token::new_space(' ', trace_key), + _ => token::Token::new_letter(c, trace_key), + }; + self.expansions_mut().push(token); + } + } } + impl ExpansionInput { #[inline] pub fn unexpanded(&mut self) -> &mut UnexpandedStream { diff --git a/crates/texlang-stdlib/Cargo.toml b/crates/texlang-stdlib/Cargo.toml index dd484c8a..897c11fb 100644 --- a/crates/texlang-stdlib/Cargo.toml +++ b/crates/texlang-stdlib/Cargo.toml @@ -22,8 +22,8 @@ texlang-core = { path = "../texlang-core" } # Nice terminal output. colored = "2" -serde_json = {version="1.0", optional = true} serde = {version="^1.0", optional = true} +serde_json = {version="1.0", optional = true} rmp-serde = {version="^1.0", optional = true} [features] diff --git a/crates/texlang-stdlib/src/alloc.rs b/crates/texlang-stdlib/src/alloc.rs index e8d52a73..5362733a 100644 --- a/crates/texlang-stdlib/src/alloc.rs +++ b/crates/texlang-stdlib/src/alloc.rs @@ -106,7 +106,7 @@ fn singleton_mut_ref_fn>( } /// Return a getter provider for the `\newInt` command. -/// +/// /// The initial commands for a VM must include this command in order for /// the allocation component to be serializable. pub fn get_newint_getter_provider>() -> command::BuiltIn { @@ -175,7 +175,7 @@ fn array_element_mut_ref_fn>( } /// Return a getter provider for the `\newIntArray` command. -/// +/// /// The initial commands for a VM must include this command in order to support /// the allocation component to be serializable. pub fn get_newintarray_getter_provider>() -> command::BuiltIn { @@ -183,7 +183,8 @@ pub fn get_newintarray_getter_provider>() -> command: array_element_ref_fn, array_element_mut_ref_fn, variable::IndexResolver::Dynamic(resolve), - ).into() + ) + .into() } #[cfg(test)] @@ -209,7 +210,10 @@ mod test { ("newInt", get_newint()), ("newInt_getter_provider_\u{0}", get_newint_getter_provider()), ("newIntArray", get_newintarray()), - ("newIntArray_getter_provider_\u{0}", get_newintarray_getter_provider()), + ( + "newIntArray_getter_provider_\u{0}", + get_newintarray_getter_provider(), + ), ("the", get_the()), ]) } diff --git a/crates/texlang-stdlib/src/io.rs b/crates/texlang-stdlib/src/io.rs index d1a3f41f..0f063d71 100644 --- a/crates/texlang-stdlib/src/io.rs +++ b/crates/texlang-stdlib/src/io.rs @@ -17,7 +17,7 @@ fn input_fn( ) -> Result, Box> { let file_location = FileLocation::parse(input)?; let (file_path, source_code) = read_file(input_token, input.vm(), file_location, ".tex")?; - input.push_source(input_token, file_path, source_code)?; + input.push_source(input_token, file_path.into(), source_code)?; Ok(Vec::new()) } diff --git a/crates/texlang-stdlib/src/job.rs b/crates/texlang-stdlib/src/job.rs new file mode 100644 index 00000000..b5bc3eb4 --- /dev/null +++ b/crates/texlang-stdlib/src/job.rs @@ -0,0 +1,155 @@ +use std::{ + collections::HashMap, + ffi::OsString, + path::{Path, PathBuf}, +}; +use texlang_core::traits::*; +use texlang_core::*; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Component { + #[cfg_attr(feature = "serde", serde(skip))] + job_name: Option, + default_job_name: PathBuf, + dump_format: i32, + dump_validate: i32, + #[cfg_attr(feature = "serde", serde(skip))] + num_dumps: usize, +} + +impl Default for Component { + fn default() -> Self { + Self { + job_name: None, + default_job_name: "jobname".into(), + dump_format: 0, + dump_validate: 0, + num_dumps: 0, + } + } +} + +impl Component { + pub fn job_name(&self) -> &Path { + match &self.job_name { + None => &self.default_job_name, + Some(job_name) => job_name, + } + } + + // TODO: this should be called after the first \input. + // Probably should add a hook to the VM for this case. + // TODO: maybe this should be bundled with \input + /// Set the job name based on the provided file path. + pub fn set_job_name(&mut self, file_path: &Path) { + if let Some(file_stem) = file_path.file_stem() { + self.job_name = Some(file_stem.into()) + } + } +} + +/// Get the `\jobname` primitive. +pub fn get_jobname>() -> command::BuiltIn { + command::BuiltIn::new_expansion(|token, input| { + let job_name: String = input + .state() + .component() + .job_name() + .to_string_lossy() + .into(); + input.push_string_tokens(token, &job_name); + Ok(vec![]) + }) +} + +/// Get the `\dump` primitive. +#[cfg(feature = "serde")] +pub fn get_dump + serde::Serialize + serde::de::DeserializeOwned>( +) -> command::BuiltIn { + command::BuiltIn::new_execution(dump_primitive_fn) +} + +#[cfg(feature = "serde")] +fn dump_primitive_fn< + S: HasComponent + serde::Serialize + serde::de::DeserializeOwned, +>( + _: token::Token, + input: &mut vm::ExecutionInput, +) -> command::Result<()> { + let component = input.state().component(); + let mut job_name: OsString = component.job_name().into(); + let num_dumps = component.num_dumps; + if num_dumps > 0 { + job_name.push(format!["-{}", num_dumps + 1]); + } + let mut output_file: PathBuf = job_name.into(); + output_file.set_extension(if component.dump_format == 0 { + "fmt" + } else { + "fmt.json" + }); + + // TODO: error handle all these serialization errors. + let serialized = match component.dump_format { + 0 => rmp_serde::encode::to_vec(input.vm()).unwrap(), + 1 => serde_json::to_vec_pretty(input.vm()).unwrap(), + i => { + return Err(error::SimpleFailedPreconditionError::new(format![ + r"\dumpFormat has invalid value {i}", + ]) + .with_note(r"\dumpFormat must be either 0 (serialize with message pack) or 1 (serialize with json)") + .into()) + } + }; + + if component.dump_validate != 0 { + let initial_built_ins: HashMap<&str, command::BuiltIn> = input + .vm() + .commands_map + .initial_built_ins() + .iter() + .map(|(cs_name, command)| { + ( + input.vm().cs_name_interner().resolve(*cs_name).unwrap(), + command.clone(), + ) + }) + .collect(); + match component.dump_format { + 0 => { + let mut deserializer = rmp_serde::decode::Deserializer::from_read_ref(&serialized); + vm::VM::::deserialize(&mut deserializer, initial_built_ins); + } + 1 => { + let mut deserializer = serde_json::Deserializer::from_slice(&serialized); + vm::VM::::deserialize(&mut deserializer, initial_built_ins); + } + _ => unreachable!(), + }; + } + + // TODO: error handle file write + input + .vm() + .file_system + .write_bytes(&output_file, &serialized) + .unwrap(); + input.state_mut().component_mut().num_dumps += 1; + Ok(()) +} + +pub fn get_dumpformat>() -> command::BuiltIn { + variable::Command::new_singleton( + |state: &S, _| &state.component().dump_format, + |state: &mut S, _| &mut state.component_mut().dump_format, + ) + .into() +} + +pub fn get_dumpvalidate>() -> command::BuiltIn { + variable::Command::new_singleton( + |state: &S, _| &state.component().dump_validate, + |state: &mut S, _| &mut state.component_mut().dump_validate, + ) + .into() +} diff --git a/crates/texlang-stdlib/src/lib.rs b/crates/texlang-stdlib/src/lib.rs index 6308e1c3..06a95693 100644 --- a/crates/texlang-stdlib/src/lib.rs +++ b/crates/texlang-stdlib/src/lib.rs @@ -19,6 +19,7 @@ pub mod conditional; pub mod def; pub mod expansion; pub mod io; +pub mod job; pub mod math; pub mod prefix; pub mod registers; @@ -38,6 +39,7 @@ pub struct StdLibState { alloc: alloc::Component, catcode: catcode::Component, conditional: conditional::Component, + pub job: job::Component, prefix: prefix::Component, registers: registers::Component, repl: repl::Component, @@ -93,6 +95,8 @@ impl StdLibState { ("day", time::get_day()), ("def", def::get_def()), ("divide", math::get_divide()), + ("dumpFormat", job::get_dumpformat()), + ("dumpValidate", job::get_dumpvalidate()), // ("else", conditional::get_else()), ("expandafter", expansion::get_expandafter_optimized()), @@ -110,6 +114,8 @@ impl StdLibState { ("iftrue", conditional::get_if_true()), ("input", io::get_input()), // + ("jobname", job::get_jobname()), + // ("let", alias::get_let()), ("long", prefix::get_long()), // @@ -117,9 +123,15 @@ impl StdLibState { ("multiply", math::get_multiply()), // ("newInt", alloc::get_newint()), - ("newInt_getter_provider_\u{0}", alloc::get_newint_getter_provider()), + ( + "newInt_getter_provider_\u{0}", + alloc::get_newint_getter_provider(), + ), ("newIntArray", alloc::get_newintarray()), - ("newIntArray_getter_provider_\u{0}", alloc::get_newintarray_getter_provider()), + ( + "newIntArray_getter_provider_\u{0}", + alloc::get_newintarray_getter_provider(), + ), ("noexpand", expansion::get_noexpand()), // ("or", conditional::get_or()), @@ -147,6 +159,7 @@ implement_has_component![ (alloc::Component, alloc), (catcode::Component, catcode), (conditional::Component, conditional), + (job::Component, job), (prefix::Component, prefix), (registers::Component, registers), (repl::Component, repl), @@ -265,7 +278,7 @@ mod tests { r"\def\else{}\ifodd 2 \else should be skipped \fi", r"" )), - serde_tests((serde_sanity_check, r"\def\HW{Hello World} ", r"\HW"),), + serde_tests((serde_sanity, r"\def\HW{Hello World} ", r"\HW"),), ]; #[test] diff --git a/crates/texlang-stdlib/src/registers.rs b/crates/texlang-stdlib/src/registers.rs index 4e16fc77..f91ec64e 100644 --- a/crates/texlang-stdlib/src/registers.rs +++ b/crates/texlang-stdlib/src/registers.rs @@ -13,7 +13,7 @@ pub struct Component( // We currently box the values because putting them directly on the stack causes the // message pack decoder to stack overflow. It's a pity that we have to pay a runtime // cost due to this, and it would be nice to fix the issue another way. - Box<[T; N]> + Box<[T; N]>, ); #[cfg(feature = "serde")] diff --git a/crates/texlang-stdlib/src/testing.rs b/crates/texlang-stdlib/src/testing.rs index ac69173f..5b4a7a0b 100644 --- a/crates/texlang-stdlib/src/testing.rs +++ b/crates/texlang-stdlib/src/testing.rs @@ -216,7 +216,9 @@ pub fn run_serde_test( output_1_1.append(&mut output_1_2); let mut vm_2 = initialize_vm(&options); - let output_2 = crate::testing::execute_source_code(&mut vm_2,format!["{input_1}{input_2}"], &options).unwrap(); + let output_2 = + crate::testing::execute_source_code(&mut vm_2, format!["{input_1}{input_2}"], &options) + .unwrap(); compare_output(output_1_1, &vm_1, output_2, &vm_2) } @@ -312,6 +314,9 @@ impl vm::FileSystem for InMemoryFileSystem { Some(content) => Ok(content.clone()), } } + fn write_bytes(&self, _: &std::path::Path, _: &[u8]) -> std::io::Result<()> { + unimplemented!() + } } impl InMemoryFileSystem {