From 5186bc403050c95c91db6c65948751e33aaf0936 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 21 Sep 2023 21:37:26 +0200 Subject: [PATCH] Builtin modules This changeset adds a fixed version of the Numbat standard library (`prelude`) to the binary itself. This way, the installation procedure for the typical user is greatly simplified. They just need one executable. Customization is still possible, as described here: https://numbat.dev/doc/cli-customization.html closes #165 --- Cargo.lock | 159 ++++++++++++++++++++++- README.md | 14 ++- book/src/cli-customization.md | 26 ++-- book/src/cli-installation.md | 14 +-- numbat-cli/src/main.rs | 19 +-- numbat-wasm/Cargo.lock | 210 ++++++++++++++++++++++++++++++- numbat-wasm/src/lib.rs | 5 +- numbat-wasm/src/wasm_importer.rs | 48 ------- numbat/Cargo.toml | 1 + numbat/examples/unit_graph.rs | 5 +- numbat/src/lib.rs | 4 +- numbat/src/module_importer.rs | 101 +++++++++++++++ numbat/src/resolver.rs | 52 +------- numbat/tests/common.rs | 14 +-- 14 files changed, 518 insertions(+), 154 deletions(-) delete mode 100644 numbat-wasm/src/wasm_importer.rs create mode 100644 numbat/src/module_importer.rs diff --git a/Cargo.lock b/Cargo.lock index cbc40b2a..06a231f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "1.6.2" @@ -237,19 +246,68 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", ] [[package]] @@ -348,6 +406,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -558,6 +626,7 @@ dependencies = [ "num-traits", "numbat-exchange-rates", "pretty_dtoa", + "rust-embed", "strsim", "thiserror", "unicode-ident", @@ -572,7 +641,7 @@ dependencies = [ "assert_cmd", "clap", "colored", - "dirs", + "dirs 5.0.1", "itertools", "numbat", "predicates", @@ -746,6 +815,41 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust-embed" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustix" version = "0.38.14" @@ -822,6 +926,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "700de91d5fd6091442d00fdd9ee790af6d4f0f480562b0f5a1e8f59e90aafe73" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -858,6 +971,26 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs 4.0.0", +] + [[package]] name = "smallvec" version = "1.11.1" @@ -953,6 +1086,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -1009,6 +1148,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -1018,6 +1163,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/README.md b/README.md index eae5fa87..1060c8a4 100644 --- a/README.md +++ b/README.md @@ -141,10 +141,6 @@ please consider contributing. Or try [GNU units](https://www.gnu.org/software/un ## Development -Clone the repository and make sure to properly set up the Numbat modules path. This can -either be done by copying the `modules/` folder to the [appropriate location](https://numbat.dev/doc/cli-customization.html#module-paths) -on your operating system, or by pointing the `NUMBAT_MODULES_PATH` environment variable to the `modules/` folder. - Run Numbat CLI ``` cargo run -- @@ -159,3 +155,13 @@ Run all tests ``` cargo test ``` + +### Working on the `prelude` + +If you are working on [Numbats standard library](modules/), it is convenient to point +the `NUMBAT_MODULES_PATH` environment variable to the `modules/` folder. This way, +you don't have to recompile Numbat to see your changes. + +Alternatively, you can create a symlink from `~/.config/numbat/modules` to the `modules/` +folder in the repository (see [this page](https://numbat.dev/doc/cli-customization.html#module-paths) +for the standard paths on other operating systems. diff --git a/book/src/cli-customization.md b/book/src/cli-customization.md index 63d641a3..fb4cf583 100644 --- a/book/src/cli-customization.md +++ b/book/src/cli-customization.md @@ -2,9 +2,9 @@ ## Startup -By default, Numbat will load the following files during startup: +By default, Numbat will load the following modules/files during startup, in order: -- Numbat Prelude (a module called `prelude`, from `/prelude.nbt`) +- Numbat Prelude (a module called `prelude`, either from `/prelude.nbt` if available, or the builtin version) - The user initialization file, if available (a file called `init.nbt` from `/init.nbt`) ### Config path @@ -19,12 +19,14 @@ Numbat's configuration folder (`` above) can be found under: ## Module paths -Numbat will load modules from the following directories (`` above). +Numbat will load modules from the following sources. Entries higher up in the list take precedence. +* `$NUMBAT_MODULES_PATH` — If present, this environment variable can point to a single directory or contain a `:`-separated list of paths * `/modules` — User-customized module folder -* `/usr/share/numbat/modules` — on Linux and macOS -* `C:\Program Files\numbat\modules` — on Windows +* `/usr/share/numbat/modules` — System-wide module folder (Linux and macOS) +* `C:\Program Files\numbat\modules` — System-wide module folder (Windows) +* Builtin modules inside the `numbat` binary ## Customization @@ -36,17 +38,23 @@ create a `init.nbt` file in your config folder (e.g. `~/.config/numbat/init.nbt` ### Custom modules You can also create your own modules that can be loaded on demand. To this end, -create a new file, say `/finance.nbt` in one of the module folders (e.g. `~/.config/numbat/modules/finance.nbt` on Linux). This module can then be loaded using +create a new file, say `/user/finance.nbt` in one of the module folders +(e.g. `~/.config/numbat/modules/custom/finance.nbt` on Linux). This module can then be +loaded using ``` numbat -use finance +use custom::finance ``` in your Numbat scripts or in the REPL. You can also load custom modules from `init.nbt` if you want to have them available all the time. -You can also organize modules into subfolders (e.g. `/finance/functions.nbt`). In that case, you can load them using +You can also organize modules into subfolders (e.g. `/custom/finance/functions.nbt`). +In that case, you can load them using ``` numbat -use finance::functions +use custom::finance::functions ``` + +In fact, the `custom` folder is just a convention to avoid name clashes with the +[standard library](https://github.com/sharkdp/numbat/tree/master/modules). diff --git a/book/src/cli-installation.md b/book/src/cli-installation.md index 0089614e..8bf4335c 100644 --- a/book/src/cli-installation.md +++ b/book/src/cli-installation.md @@ -17,14 +17,6 @@ to try it immediately (unpacks to `/tmp` and executes from there). For a proper installation, download the latest release [here](https://github.com/sharkdp/numbat/releases). -Unpack the archive and move the `modules/` folder to `~/.config/numbat`, -or one of the [other supported directories](./cli-customization.md#module-paths). - -``` bash -mkdir -p ~/.config/numbat -cp -r numbat/modules ~/.config/numbat/ -``` - ## From source Clone the Git repository, and build Numbat with `cargo`: @@ -35,10 +27,8 @@ cd numbat/ cargo install -f --path numbat-cli ``` -And copy the `modules/` folder to `~/.config/numbat/`, or the -[respective folder on your operating system](./cli-customization.md#module-paths). +Or install the latest release using ``` bash -mkdir -p ~/.config/numbat -cp -r modules/ ~/.config/numbat/ +cargo install numbat-cli ``` diff --git a/numbat-cli/src/main.rs b/numbat-cli/src/main.rs index 4adad7dc..10f59056 100644 --- a/numbat-cli/src/main.rs +++ b/numbat-cli/src/main.rs @@ -8,8 +8,9 @@ use highlighter::NumbatHighlighter; use numbat::diagnostic::ErrorDiagnostic; use numbat::markup as m; +use numbat::module_importer::{BuiltinModuleImporter, ChainedImporter, FileSystemImporter}; use numbat::pretty_print::PrettyPrint; -use numbat::resolver::{CodeSource, FileSystemImporter, ResolverError}; +use numbat::resolver::CodeSource; use numbat::{Context, ExitStatus, InterpreterResult, NumbatError}; use numbat::{InterpreterSettings, NameResolutionError, RuntimeError, Type}; @@ -100,11 +101,16 @@ impl Cli { fn new() -> Self { let args = Args::parse(); - let mut importer = FileSystemImporter::default(); + let mut fs_importer = FileSystemImporter::default(); for path in Self::get_modules_paths() { - importer.add_path(path); + fs_importer.add_path(path); } + let importer = ChainedImporter::new( + Box::new(fs_importer), + Box::new(BuiltinModuleImporter::default()), + ); + let mut context = Context::new(importer); context.set_debug(args.debug); @@ -414,12 +420,6 @@ impl Cli { } Err(NumbatError::ResolverError(e)) => { self.print_diagnostic(e.clone()); - if matches!(&e, ResolverError::UnknownModule(_, module_path) if module_path.0 == &["prelude"]) - { - eprintln!("Make sure that you have installed Numbat's standard library"); - eprintln!("in one of the standard locations, or set NUMBAT_MODULES_PATH."); - eprintln!("For details, see https://numbat.dev/doc/cli-installation.html"); - } execution_mode.exit_status_in_case_of_error() } Err(NumbatError::NameResolutionError( @@ -465,6 +465,7 @@ impl Cli { } paths.push(Self::get_config_path().join("modules")); + if cfg!(unix) { paths.push("/usr/share/numbat/modules".into()); } else { diff --git a/numbat-wasm/Cargo.lock b/numbat-wasm/Cargo.lock index fc7adb72..8dd69555 100644 --- a/numbat-wasm/Cargo.lock +++ b/numbat-wasm/Cargo.lock @@ -21,6 +21,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.13.0" @@ -74,6 +89,55 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "either" version = "1.8.1" @@ -95,6 +159,27 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + [[package]] name = "heck" version = "0.4.1" @@ -136,9 +221,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] @@ -226,7 +311,7 @@ dependencies = [ [[package]] name = "numbat" -version = "1.5.0" +version = "1.6.0" dependencies = [ "codespan-reporting", "heck", @@ -237,6 +322,7 @@ dependencies = [ "num-traits", "numbat-exchange-rates", "pretty_dtoa", + "rust-embed", "strsim", "thiserror", "unicode-ident", @@ -245,7 +331,7 @@ dependencies = [ [[package]] name = "numbat-exchange-rates" -version = "0.1.0" +version = "0.2.0" dependencies = [ "attohttpc", "quick-xml", @@ -297,9 +383,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", ] @@ -313,6 +399,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "ring" version = "0.16.20" @@ -328,6 +434,41 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust-embed" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustls" version = "0.21.7" @@ -356,6 +497,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "700de91d5fd6091442d00fdd9ee790af6d4f0f480562b0f5a1e8f59e90aafe73" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -372,6 +522,26 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs", +] + [[package]] name = "spin" version = "0.5.2" @@ -439,6 +609,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -495,6 +671,28 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" +[[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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.87" diff --git a/numbat-wasm/src/lib.rs b/numbat-wasm/src/lib.rs index ceda8ae3..e6fd13cf 100644 --- a/numbat-wasm/src/lib.rs +++ b/numbat-wasm/src/lib.rs @@ -1,13 +1,12 @@ mod jquery_terminal_formatter; mod utils; -mod wasm_importer; use numbat::diagnostic::ErrorDiagnostic; +use numbat::module_importer::BuiltinModuleImporter; use std::sync::{Arc, Mutex}; use wasm_bindgen::prelude::*; use jquery_terminal_formatter::{jt_format, JqueryTerminalFormatter}; -use wasm_importer::WasmImporter; use numbat::markup::Formatter; use numbat::pretty_print::PrettyPrint; @@ -36,7 +35,7 @@ pub struct Numbat { #[wasm_bindgen] impl Numbat { pub fn new() -> Self { - let mut ctx = Context::new(WasmImporter {}); + let mut ctx = Context::new(BuiltinModuleImporter::default()); let _ = ctx.interpret("use prelude", CodeSource::Internal).unwrap(); Numbat { ctx } } diff --git a/numbat-wasm/src/wasm_importer.rs b/numbat-wasm/src/wasm_importer.rs deleted file mode 100644 index 20793cb0..00000000 --- a/numbat-wasm/src/wasm_importer.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::path::PathBuf; - -use numbat::resolver::{ModuleImporter, ModulePath}; - -#[derive(Debug, Clone, Default)] -pub struct WasmImporter {} - -impl ModuleImporter for WasmImporter { - fn import(&self, path: &ModulePath) -> Option<(String, Option)> { - // TODO: yeah, this is not great. - - let path = path.to_string(); - let code = match path.as_str() { - "core::dimensions" => include_str!("../../modules/core/dimensions.nbt"), - "core::quantity" => include_str!("../../modules/core/quantity.nbt"), - "core::scalar" => include_str!("../../modules/core/scalar.nbt"), - "math::constants" => include_str!("../../modules/math/constants.nbt"), - "math::functions" => include_str!("../../modules/math/functions.nbt"), - "math::trigonometry_extra" => include_str!("../../modules/math/trigonometry_extra.nbt"), - "physics::constants" => include_str!("../../modules/physics/constants.nbt"), - "physics::temperature_conversion" => { - include_str!("../../modules/physics/temperature_conversion.nbt") - } - "prelude" => include_str!("../../modules/prelude.nbt"), - "units::astronomical" => include_str!("../../modules/units/astronomical.nbt"), - "units::bit" => include_str!("../../modules/units/bit.nbt"), - "units::cgs" => include_str!("../../modules/units/cgs.nbt"), - "units::currencies" => include_str!("../../modules/units/currencies.nbt"), - "units::currency" => include_str!("../../modules/units/currency.nbt"), - "units::fff" => include_str!("../../modules/units/fff.nbt"), - "units::hartree" => include_str!("../../modules/units/hartree.nbt"), - "units::imperial" => include_str!("../../modules/units/imperial.nbt"), - "units::misc" => include_str!("../../modules/units/misc.nbt"), - "units::nautical" => include_str!("../../modules/units/nautical.nbt"), - "units::partsperx" => include_str!("../../modules/units/partsperx.nbt"), - "units::placeholder" => include_str!("../../modules/units/placeholder.nbt"), - "units::planck" => include_str!("../../modules/units/planck.nbt"), - "units::si" => include_str!("../../modules/units/si.nbt"), - "units::stoney" => include_str!("../../modules/units/stoney.nbt"), - "units::time" => include_str!("../../modules/units/time.nbt"), - "units::us_customary" => include_str!("../../modules/units/us_customary.nbt"), - - _ => return None, - }; - - Some((code.to_string(), None)) - } -} diff --git a/numbat/Cargo.toml b/numbat/Cargo.toml index 45d16e5b..0b04123e 100644 --- a/numbat/Cargo.toml +++ b/numbat/Cargo.toml @@ -26,6 +26,7 @@ heck = { version = "0.4.1", features = ["unicode"] } unicode-ident = "1.0.11" unicode-width = "0.1.10" libc = "0.2.147" +rust-embed = { version = "8.0.0", features = ["interpolate-folder-path"] } [dev-dependencies] approx = "0.5" diff --git a/numbat/examples/unit_graph.rs b/numbat/examples/unit_graph.rs index 02441e5b..ea8458d1 100644 --- a/numbat/examples/unit_graph.rs +++ b/numbat/examples/unit_graph.rs @@ -5,10 +5,7 @@ // use itertools::Itertools; -use numbat::{ - resolver::{CodeSource, FileSystemImporter}, - Context, -}; +use numbat::{module_importer::FileSystemImporter, resolver::CodeSource, Context}; fn main() { let mut importer = FileSystemImporter::default(); diff --git a/numbat/src/lib.rs b/numbat/src/lib.rs index ca3804e7..c950618d 100644 --- a/numbat/src/lib.rs +++ b/numbat/src/lib.rs @@ -11,6 +11,7 @@ mod interpreter; pub mod keywords; pub mod markup; mod math; +pub mod module_importer; mod name_resolution; mod number; mod parser; @@ -38,11 +39,10 @@ use diagnostic::ErrorDiagnostic; use dimension::DimensionRegistry; use interpreter::Interpreter; use keywords::KEYWORDS; +use module_importer::{ModuleImporter, NullImporter}; use prefix_transformer::Transformer; use registry::BaseRepresentationFactor; use resolver::CodeSource; -use resolver::ModuleImporter; -use resolver::NullImporter; use resolver::Resolver; use resolver::ResolverError; use thiserror::Error; diff --git a/numbat/src/module_importer.rs b/numbat/src/module_importer.rs new file mode 100644 index 00000000..7a7e084c --- /dev/null +++ b/numbat/src/module_importer.rs @@ -0,0 +1,101 @@ +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use rust_embed::RustEmbed; + +use crate::resolver::ModulePath; + +pub trait ModuleImporter { + fn import(&self, path: &ModulePath) -> Option<(String, Option)>; +} + +#[derive(Debug, Clone, Default)] +pub struct NullImporter {} + +impl ModuleImporter for NullImporter { + fn import(&self, _: &ModulePath) -> Option<(String, Option)> { + None + } +} + +#[derive(Debug, Clone, Default)] +pub struct FileSystemImporter { + root_paths: Vec, +} + +impl FileSystemImporter { + pub fn add_path>(&mut self, path: P) { + self.root_paths.push(path.as_ref().to_owned()); + } +} + +impl ModuleImporter for FileSystemImporter { + fn import(&self, module_path: &ModulePath) -> Option<(String, Option)> { + for path in &self.root_paths { + let mut path = path.clone(); + for part in &module_path.0 { + path = path.join(part); + } + + path.set_extension("nbt"); + + if let Ok(code) = fs::read_to_string(&path) { + return Some((code, Some(path.to_owned()))); + } + } + + None + } +} + +#[derive(RustEmbed)] +#[folder = "$CARGO_MANIFEST_DIR/../modules/"] +struct BuiltinAssets; + +#[derive(Debug, Clone, Default)] + +pub struct BuiltinModuleImporter {} + +impl ModuleImporter for BuiltinModuleImporter { + fn import(&self, module_path: &ModulePath) -> Option<(String, Option)> { + let mut path = PathBuf::new(); + for part in &module_path.0 { + path = path.join(part); + } + + path.set_extension("nbt"); + + BuiltinAssets::get(&path.to_string_lossy()) + .map(|embedded_file| { + let content = embedded_file.data.into_owned(); + String::from_utf8(content).expect("Numbat modules are properly UTF-8 encoded") + }) + .map(|content| (content, None)) + } +} + +pub struct ChainedImporter { + main: Box, + fallback: Box, +} + +impl ChainedImporter { + pub fn new( + main: Box, + fallback: Box, + ) -> Self { + Self { main, fallback } + } +} + +impl ModuleImporter for ChainedImporter { + fn import(&self, path: &ModulePath) -> Option<(String, Option)> { + if let result @ Some(_) = self.main.import(path) { + result + } else { + self.fallback.import(path) + } + } +} diff --git a/numbat/src/resolver.rs b/numbat/src/resolver.rs index 24e92c8e..c9a4b729 100644 --- a/numbat/src/resolver.rs +++ b/numbat/src/resolver.rs @@ -1,9 +1,8 @@ -use std::{ - fs, - path::{Path, PathBuf}, -}; +use std::path::PathBuf; -use crate::{ast::Statement, parser::parse, span::Span, ParseError}; +use crate::{ + ast::Statement, module_importer::ModuleImporter, parser::parse, span::Span, ParseError, +}; use codespan_reporting::files::SimpleFiles; use thiserror::Error; @@ -130,49 +129,6 @@ impl Resolver { } } -pub trait ModuleImporter { - fn import(&self, path: &ModulePath) -> Option<(String, Option)>; -} - -#[derive(Debug, Clone, Default)] -pub struct NullImporter {} - -impl ModuleImporter for NullImporter { - fn import(&self, _: &ModulePath) -> Option<(String, Option)> { - None - } -} - -#[derive(Debug, Clone, Default)] -pub struct FileSystemImporter { - root_paths: Vec, -} - -impl FileSystemImporter { - pub fn add_path>(&mut self, path: P) { - self.root_paths.push(path.as_ref().to_owned()); - } -} - -impl ModuleImporter for FileSystemImporter { - fn import(&self, module_path: &ModulePath) -> Option<(String, Option)> { - for path in &self.root_paths { - let mut path = path.clone(); - for part in &module_path.0 { - path = path.join(part); - } - - path.set_extension("nbt"); - - if let Ok(code) = fs::read_to_string(&path) { - return Some((code, Some(path.to_owned()))); - } - } - - None - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/numbat/tests/common.rs b/numbat/tests/common.rs index 7a967cc0..e0e36c25 100644 --- a/numbat/tests/common.rs +++ b/numbat/tests/common.rs @@ -1,14 +1,14 @@ use std::path::Path; -use numbat::{ - resolver::{CodeSource, FileSystemImporter}, - Context, -}; +use numbat::{module_importer::FileSystemImporter, resolver::CodeSource, Context}; pub fn get_test_context_without_prelude() -> Context { - let module_path = Path::new(&std::env::var_os("CARGO_MANIFEST_DIR").unwrap()) - .join("..") - .join("modules"); + let module_path = Path::new( + &std::env::var_os("CARGO_MANIFEST_DIR") + .expect("CARGO_MANIFEST_DIR variable should be set when calling 'cargo test'"), + ) + .join("..") + .join("modules"); let mut importer = FileSystemImporter::default(); importer.add_path(module_path);