├── .cargo └── config.toml ├── .gitignore ├── .rustfmt.toml ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile.toml ├── README.md ├── docs ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Development.md ├── Maintenance.md ├── QuickStart.md └── WhyRust.md ├── scripts ├── BuildPaclet.wls └── re_build_WLLibraryLink.xml ├── wolfram-library-link-sys ├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── README.md ├── build.rs ├── generated │ ├── 12.1.0 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 12.1.1 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 12.2.0 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 12.3.0 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 13.0.0 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 13.0.1 │ │ ├── Linux-ARM64 │ │ │ └── LibraryLink_bindings.rs │ │ ├── Linux-x86-64 │ │ │ └── LibraryLink_bindings.rs │ │ ├── MacOSX-ARM64 │ │ │ └── LibraryLink_bindings.rs │ │ ├── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ │ └── Windows-x86-64 │ │ │ └── LibraryLink_bindings.rs │ └── 13.2.0 │ │ ├── MacOSX-ARM64 │ │ └── LibraryLink_bindings.rs │ │ └── MacOSX-x86-64 │ │ └── LibraryLink_bindings.rs └── src │ └── lib.rs ├── wolfram-library-link ├── Cargo.toml ├── RustLink │ ├── Documentation │ │ └── English │ │ │ └── Tutorials │ │ │ └── GettingStarted.nb │ ├── Examples │ │ ├── Aborts.wlt │ │ ├── AsyncExamples.wlt │ │ ├── BasicTypes.wlt │ │ ├── DataStore.wlt │ │ ├── Docs │ │ │ ├── Convert │ │ │ │ ├── ManualWSTP.wlt │ │ │ │ └── UsingExpr.wlt │ │ │ └── EvaluateWolframCodeFromRust │ │ │ │ └── GenerateMessage.wlt │ │ ├── Expressions.wlt │ │ ├── ManagedExpressions.wlt │ │ ├── NumericArrays.wlt │ │ ├── RawFunctions.wlt │ │ └── WSTP.wlt │ ├── LibraryResources │ │ └── .include_in_git │ ├── PacletInfo.m │ └── Tests │ │ ├── DataStore.wlt │ │ ├── Images.wlt │ │ ├── NativeArgs.wlt │ │ ├── NumericArrayConversions.wlt │ │ ├── ShareCounts.wlt │ │ ├── Threading.wlt │ │ └── WSTP.wlt ├── docs │ ├── Overview.md │ └── README.md ├── examples │ ├── aborts.rs │ ├── async │ │ ├── async_file_watcher.rs │ │ └── async_file_watcher_raw.rs │ ├── basic_types.rs │ ├── data_store.rs │ ├── docs │ │ ├── README.md │ │ ├── convert │ │ │ ├── manual_wstp.rs │ │ │ └── using_expr.rs │ │ ├── evaluate_wolfram_code_from_rust │ │ │ └── generate_message.rs │ │ └── main.rs │ ├── exprs │ │ ├── basic_expressions.rs │ │ └── managed.rs │ ├── numeric_arrays.rs │ ├── raw │ │ ├── raw_librarylink_function.rs │ │ └── raw_wstp_function.rs │ ├── tests │ │ ├── README.md │ │ ├── main.rs │ │ ├── test_data_store.rs │ │ ├── test_images.rs │ │ ├── test_native_args.rs │ │ ├── test_numeric_array_conversions.rs │ │ ├── test_share_counts.rs │ │ ├── test_threading.rs │ │ └── test_wstp.rs │ └── wstp.rs ├── src │ ├── args.rs │ ├── async_tasks.rs │ ├── catch_panic.rs │ ├── data_store.rs │ ├── docs.rs │ ├── docs │ │ ├── converting_between_rust_and_wolfram_types.rs │ │ └── evaluate_wolfram_code_from_rust.rs │ ├── image.rs │ ├── lib.rs │ ├── library_data.rs │ ├── macro_utils.rs │ ├── managed.rs │ ├── numeric_array.rs │ └── rtl.rs └── wolfram-library-link-macros │ ├── Cargo.toml │ └── src │ ├── export.rs │ └── lib.rs └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | /build -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 90 2 | comment_width = 90 3 | match_block_trailing_comma = true 4 | blank_lines_upper_bound = 3 5 | merge_derives = false 6 | overflow_delimited_expr = true 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.formatOnSaveMode": "file" 4 | } 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "wolfram-library-link", 6 | "wolfram-library-link-sys", 7 | "xtask" 8 | ] -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Wolfram Research Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [config] 2 | default_to_workspace = false 3 | 4 | #======================================= 5 | # Task definitions 6 | #======================================= 7 | 8 | #------------------ 9 | # Development tasks 10 | #------------------ 11 | 12 | [tasks.paclet] 13 | dependencies = ["clean-library-resources", "build-library-resources"] 14 | script = { file = "./scripts/BuildPaclet.wls"} 15 | 16 | #-------------------- 17 | # Building the paclet 18 | #-------------------- 19 | 20 | [tasks.clean-library-resources] 21 | script = ''' 22 | rm -rf ./build/ 23 | ''' 24 | 25 | [tasks.build-library-resources] 26 | command = "cargo" 27 | args = ["build", "--examples"] 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wolfram-library-link 2 | 3 | [](https://crates.io/crates/wolfram-library-link) 4 |  5 | [](https://docs.rs/wolfram-library-link) 6 | 7 |
"]
5 | license = "MIT OR Apache-2.0"
6 | edition = "2018"
7 | readme = "README.md"
8 | repository = "https://github.com/WolframResearch/wolfram-library-link-rs"
9 | description = "Low-level FFI bindings to the Wolfram LibraryLink C API"
10 | keywords = ["wolfram-library-link", "wstp", "wolfram", "wolfram-language", "wolfram-engine"]
11 | categories = ["external-ffi-bindings", "development-tools::ffi"]
12 |
13 | # TODO: Use the 'links' key?
14 | # See: https://doc.rust-lang.org/cargo/reference/build-scripts.html#a-sys-packages
15 |
16 | [dependencies]
17 |
18 | [build-dependencies]
19 | env_logger = "0.10.0"
20 |
21 | wolfram-app-discovery = "0.4.7"
22 |
--------------------------------------------------------------------------------
/wolfram-library-link-sys/README.md:
--------------------------------------------------------------------------------
1 | # wolfram-library-link-sys
2 |
3 | Low-level FFI bindings to the
4 | [Wolfram LibraryLink C API](https://reference.wolfram.com/language/LibraryLink/tutorial/LibraryStructure.html).
5 |
6 | The [`wolfram-library-link`](https://crates.io/crates/wolfram-library-link) crate provides
7 | efficient and idiomatic Rust bindings to Wolfram LibraryLink based on these raw bindings.
--------------------------------------------------------------------------------
/wolfram-library-link-sys/build.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 |
3 | use wolfram_app_discovery::{SystemID, WolframVersion};
4 |
5 | /// Oldest Wolfram Version that wolfram-library-link is compatible with.
6 | const WOLFRAM_VERSION: WolframVersion = WolframVersion::new(13, 0, 1);
7 |
8 | fn main() {
9 | env_logger::init();
10 |
11 | // Ensure that changes to environment variables checked by wolfram-app-discovery will
12 | // cause cargo to rebuild the current crate.
13 | wolfram_app_discovery::config::set_print_cargo_build_script_directives(true);
14 |
15 | // This crate is being built by docs.rs. Skip trying to locate a WolframApp.
16 | // See: https://docs.rs/about/builds#detecting-docsrs
17 | if std::env::var("DOCS_RS").is_ok() {
18 | // Force docs.rs to use the bindings generated for this version / system.
19 | let bindings_path = make_bindings_path(WOLFRAM_VERSION, SystemID::MacOSX_x86_64);
20 |
21 | // This environment variable is included using `env!()`. wolfram-library-link-sys
22 | // will fail to build if it is not set correctly.
23 | println!(
24 | "cargo:rustc-env=CRATE_WOLFRAM_LIBRARYLINK_SYS_BINDINGS={}",
25 | bindings_path.display()
26 | );
27 |
28 | return;
29 | }
30 |
31 | //-----------------------------------------------------------
32 | // Generate or use pre-generated Rust bindings to LibraryLink
33 | //-----------------------------------------------------------
34 | // See docs/Maintenance.md for instructions on how to generate
35 | // bindings for new WL versions.
36 |
37 | let bindings_path = use_pregenerated_bindings();
38 |
39 | println!(
40 | "cargo:rustc-env=CRATE_WOLFRAM_LIBRARYLINK_SYS_BINDINGS={}",
41 | bindings_path.display()
42 | );
43 | }
44 |
45 | //========================================================================
46 | // Tell `lib.rs` where to find the file containing the WSTP Rust bindings.
47 | //========================================================================
48 |
49 | //-----------------------
50 | // Pre-generated bindings
51 | //-----------------------
52 |
53 | fn use_pregenerated_bindings() -> PathBuf {
54 | let system_id = SystemID::try_from_rust_target(&std::env::var("TARGET").unwrap())
55 | .expect("unable to get System ID for target system");
56 |
57 | // FIXME: Check that this file actually exists, and generate a nicer error if it
58 | // doesn't.
59 |
60 | let bindings_path = make_bindings_path(WOLFRAM_VERSION, system_id);
61 |
62 | println!("cargo:rerun-if-changed={}", bindings_path.display());
63 |
64 | if !bindings_path.is_file() {
65 | println!(
66 | "
67 | ==== ERROR: wolfram-library-link-sys =====
68 |
69 | Rust bindings for Wolfram LibraryLink for target configuration:
70 |
71 | WolframVersion: {}
72 | SystemID: {}
73 |
74 | have not been pre-generated.
75 |
76 | See wolfram-library-link-sys/generated/ for a listing of currently available targets.
77 |
78 | =========================================
79 | ",
80 | WOLFRAM_VERSION, system_id
81 | );
82 | panic!("");
83 | }
84 |
85 | bindings_path
86 | }
87 |
88 | /// Path (relative to the crate root directory) to the bindings file.
89 | fn make_bindings_path(wolfram_version: WolframVersion, system_id: SystemID) -> PathBuf {
90 | // Path (relative to the crate root directory) to the bindings file.
91 | let bindings_path = PathBuf::from("generated")
92 | .join(&wolfram_version.to_string())
93 | .join(system_id.as_str())
94 | .join("LibraryLink_bindings.rs");
95 |
96 | println!(
97 | "cargo:warning=info: using LibraryLink bindings from: {}",
98 | bindings_path.display()
99 | );
100 |
101 | let absolute_bindings_path =
102 | PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(&bindings_path);
103 |
104 | absolute_bindings_path
105 | }
106 |
--------------------------------------------------------------------------------
/wolfram-library-link-sys/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Automatically generated bindings to the Wolfram LibraryLink C API.
2 |
3 | #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types)]
4 | #![allow(deref_nullptr)]
5 |
6 | // The name of this file comes from `build.rs`.
7 | include!(env!("CRATE_WOLFRAM_LIBRARYLINK_SYS_BINDINGS"));
8 |
--------------------------------------------------------------------------------
/wolfram-library-link/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wolfram-library-link"
3 | version = "0.2.10"
4 | authors = ["Connor Gray "]
5 | license = "MIT OR Apache-2.0"
6 | edition = "2021"
7 | readme = "../README.md"
8 | repository = "https://github.com/WolframResearch/wolfram-library-link-rs"
9 | description = "Bindings to Wolfram LibraryLink"
10 | keywords = ["wolfram-library-link", "wstp", "wolfram", "wolfram-language", "wolfram-engine"]
11 | categories = ["external-ffi-bindings", "development-tools::ffi"]
12 |
13 | include = [
14 | "/src",
15 | "/examples",
16 | # Files in this directory are included in the `wolfram_library_link::docs` module
17 | # via `#[doc = include_str!()]`, so these need to be included in the published crate
18 | # in order for the docs.rs build to succeed.
19 | "/RustLink/Examples/Docs"
20 | ]
21 |
22 | [dependencies]
23 | wolfram-library-link-macros = { version = "0.2.10", path = "./wolfram-library-link-macros" }
24 |
25 | wolfram-library-link-sys = { version = "0.2.10", path = "../wolfram-library-link-sys" }
26 |
27 | wstp = "0.2.8"
28 | wolfram-expr = "0.1.0"
29 |
30 | once_cell = "1.8.0"
31 | static_assertions = "1.1.0"
32 | ref-cast = "1.0.6"
33 |
34 | backtrace = { version = "^0.3.46", optional = true }
35 | inventory = { version = "0.2.1", optional = true }
36 | process_path = { version = "0.1.3", optional = true }
37 |
38 | [dev-dependencies]
39 |
40 | [features]
41 | default = ["panic-failure-backtraces", "automate-function-loading-boilerplate"]
42 | nightly = []
43 |
44 | panic-failure-backtraces = ["backtrace"]
45 | automate-function-loading-boilerplate = ["inventory", "process_path", "wolfram-library-link-macros/automate-function-loading-boilerplate"]
46 |
47 |
48 | #=======================================
49 | # Examples
50 | #=======================================
51 |
52 | [[example]]
53 | name = "basic_types"
54 | crate-type = ["cdylib"]
55 | required-features = ["automate-function-loading-boilerplate"]
56 |
57 | [[example]]
58 | name = "numeric_arrays"
59 | crate-type = ["cdylib"]
60 |
61 | [[example]]
62 | name = "data_store"
63 | crate-type = ["cdylib"]
64 |
65 | [[example]]
66 | name = "aborts"
67 | crate-type = ["cdylib"]
68 |
69 | [[example]]
70 | name = "wstp_example" # avoid "libwstp.dylib", which seems too generic.
71 | path = "examples/wstp.rs"
72 | crate-type = ["cdylib"]
73 | required-features = ["automate-function-loading-boilerplate"]
74 |
75 | #-----------------------------
76 | # Raw (unsafe, low-level) APIs
77 | #-----------------------------
78 |
79 | [[example]]
80 | name = "raw_wstp_function"
81 | path = "examples/raw/raw_wstp_function.rs"
82 | crate-type = ["cdylib"]
83 |
84 | [[example]]
85 | name = "raw_librarylink_function"
86 | path = "examples/raw/raw_librarylink_function.rs"
87 | crate-type = ["cdylib"]
88 |
89 | #------------
90 | # Expressions
91 | #------------
92 |
93 | [[example]]
94 | name = "basic_expressions"
95 | path = "examples/exprs/basic_expressions.rs"
96 | crate-type = ["cdylib"]
97 |
98 | [[example]]
99 | name = "managed_exprs"
100 | path = "examples/exprs/managed.rs"
101 | crate-type = ["cdylib"]
102 | required-features = ["automate-function-loading-boilerplate"]
103 |
104 | #---------------
105 | # Async examples
106 | #---------------
107 |
108 | [[example]]
109 | name = "async_file_watcher"
110 | path = "examples/async/async_file_watcher.rs"
111 | crate-type = ["cdylib"]
112 |
113 | [[example]]
114 | name = "async_file_watcher_raw"
115 | path = "examples/async/async_file_watcher_raw.rs"
116 | crate-type = ["cdylib"]
117 |
118 | #---------------
119 | # Examples from the wolfram_library_link::docs module
120 | #---------------
121 |
122 | [[example]]
123 | name = "wll_docs"
124 | path = "examples/docs/main.rs"
125 | crate-type = ["cdylib"]
126 |
127 | #---------------
128 | # Tests -- see example/tests/README.md
129 | #---------------
130 |
131 | [[example]]
132 | name = "library_tests"
133 | path = "examples/tests/main.rs"
134 | crate-type = ["cdylib"]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/Aborts.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | Test[
4 | (* Use TimeConstrained to start an abort that will trigger after we've started
5 | executing the Rust library function. *)
6 | TimeConstrained[
7 | LibraryFunctionLoad[
8 | "libaborts",
9 | "wait_for_abort",
10 | {},
11 | Integer
12 | ][]
13 | ,
14 | (* Wait for a tenth of a second. *)
15 | 0.25
16 | ]
17 | ,
18 | $Aborted
19 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/AsyncExamples.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | (* Test the async_file_watcher_raw.rs example. *)
4 | Test[
5 | delay = 100;
6 | file = CreateFile[];
7 |
8 | $changes = {};
9 |
10 | eventHandler[
11 | taskObject_,
12 | "change",
13 | {modTime_}
14 | ] := AppendTo[$changes, modTime];
15 |
16 | (* Begin the background task. *)
17 | task = Internal`CreateAsynchronousTask[
18 | LibraryFunctionLoad[
19 | "libasync_file_watcher_raw",
20 | "start_file_watcher",
21 | {Integer, "UTF8String"},
22 | Integer
23 | ],
24 | {delay, file},
25 | eventHandler
26 | ];
27 |
28 | (* Modify the watched file. *)
29 | Put[1, file];
30 | expectedModifiedTime = UnixTime[];
31 |
32 | (* Ensure the file modification check has time to run. *)
33 | Pause[Quantity[2 * delay, "Milliseconds"]];
34 |
35 | StopAsynchronousTask[task];
36 |
37 | $changes
38 | ,
39 | {expectedModifiedTime}
40 | ]
41 |
42 | (* Test the async_file_watcher.rs example. This is identical to the above test, except the
43 | example implementation uses the safe wrappers. *)
44 | Test[
45 | delay = 100;
46 | file = CreateFile[];
47 |
48 | $changes2 = {};
49 |
50 | eventHandler[
51 | taskObject_,
52 | "change",
53 | {modTime_}
54 | ] := AppendTo[$changes2, modTime];
55 |
56 | (* Begin the background task. *)
57 | task = Internal`CreateAsynchronousTask[
58 | LibraryFunctionLoad[
59 | "libasync_file_watcher",
60 | "start_file_watcher",
61 | {Integer, "UTF8String"},
62 | Integer
63 | ],
64 | {delay, file},
65 | eventHandler
66 | ];
67 |
68 | (* Modify the watched file. *)
69 | Put[1, file];
70 | expectedModifiedTime = UnixTime[];
71 |
72 | (* Ensure the file modification check has time to run. *)
73 | Pause[Quantity[2 * delay, "Milliseconds"]];
74 |
75 | StopAsynchronousTask[task];
76 |
77 | $changes2
78 | ,
79 | {expectedModifiedTime}
80 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/BasicTypes.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | (* Test the loader function generated by `generate_loader!`. *)
4 | TestMatch[
5 | load = LibraryFunctionLoad[
6 | "libbasic_types",
7 | "load_basic_types_functions",
8 | LinkObject,
9 | LinkObject
10 | ];
11 |
12 | $functions = load["libbasic_types"];
13 |
14 | Sort[$functions]
15 | ,
16 | <|
17 | "add2" -> LibraryFunction[_, "add2", {Integer, Integer}, Integer],
18 | "add3" -> LibraryFunction[_, "add3", {Integer, Integer, Integer}, Integer],
19 | "positive_i64" -> LibraryFunction[
20 | _,
21 | "positive_i64",
22 | {{LibraryDataType[NumericArray, "Integer64"], "Constant"}},
23 | LibraryDataType[NumericArray, "UnsignedInteger8"]
24 | ],
25 | "reverse_string" -> LibraryFunction[
26 | _,
27 | "reverse_string",
28 | {"UTF8String"},
29 | "UTF8String"
30 | ],
31 | "square" -> LibraryFunction[_, "square", {Integer}, Integer],
32 | "total_i64" -> LibraryFunction[
33 | _,
34 | "total_i64",
35 | {{LibraryDataType[NumericArray, "Integer64"], "Constant"}},
36 | Integer
37 | ],
38 | "xkcd_get_random_number" -> LibraryFunction[_, "xkcd_get_random_number", {}, Integer]
39 | |>
40 | ]
41 |
42 | Test[
43 | square = $functions["square"];
44 |
45 | square[11]
46 | ,
47 | 121
48 | ]
49 |
50 | Test[
51 | reverseString = $functions["reverse_string"];
52 |
53 | reverseString["hello world"]
54 | ,
55 | "dlrow olleh"
56 | ]
57 |
58 | Test[
59 | add2 = $functions["add2"];
60 |
61 | add2[3, 3]
62 | ,
63 | 6
64 | ]
65 |
66 | Test[
67 | totalI64 = $functions["total_i64"];
68 |
69 | totalI64[NumericArray[Range[100], "Integer64"]]
70 | ,
71 | 5050
72 | ]
73 |
74 | Test[
75 | positiveQ = $functions["positive_i64"];
76 |
77 | positiveQ[NumericArray[{0, 1, -2, 3, 4, -5}, "Integer64"]]
78 | ,
79 | NumericArray[{0, 1, 0, 1, 1, 0}, "UnsignedInteger8"]
80 | ]
81 |
82 | Test[
83 | randomNumber = $functions["xkcd_get_random_number"];
84 |
85 | randomNumber[]
86 | ,
87 | 4
88 | ]
89 |
90 | Test[
91 | rawSquare = LibraryFunctionLoad[
92 | "libbasic_types",
93 | "raw_square",
94 | {Integer},
95 | Integer
96 | ];
97 |
98 | rawSquare[50]
99 | ,
100 | 2500
101 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/DataStore.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | Test[
4 | LibraryFunctionLoad[
5 | "libdata_store",
6 | "string_join",
7 | {"DataStore"},
8 | String
9 | ][
10 | Developer`DataStore["hello", " ", "world"]
11 | ]
12 | ,
13 | "hello world"
14 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/Docs/Convert/ManualWSTP.wlt:
--------------------------------------------------------------------------------
1 | VerificationTest[
2 | createPoint = LibraryFunctionLoad["libwll_docs", "create_point", LinkObject, LinkObject];
3 |
4 | createPoint[]
5 | ,
6 | Point[{1.0, 2.0}]
7 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/Docs/Convert/UsingExpr.wlt:
--------------------------------------------------------------------------------
1 | VerificationTest[
2 | createPoint = LibraryFunctionLoad["libwll_docs", "create_point2", LinkObject, LinkObject];
3 |
4 | createPoint[]
5 | ,
6 | Point[{3.0, 4.0}]
7 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/Docs/EvaluateWolframCodeFromRust/GenerateMessage.wlt:
--------------------------------------------------------------------------------
1 | MySymbol::msg = "This is a message generated from ``";
2 |
3 | (* FIXME: For some reason, the test below fails with the following message unless
4 | we _save the result_ of calling Links[]:
5 | LinkObject::linkd: Unable to communicate with closed link LinkObject[...]
6 | Note that this only happens when running the tests using
7 | `wolfram-cli paclet test`, so it's likely this is some unknown conflict.
8 | *)
9 | before = Links[];
10 |
11 | VerificationTest[
12 | generateMessage = LibraryFunctionLoad[
13 | "libwll_docs", "generate_message",
14 | LinkObject, LinkObject
15 | ];
16 |
17 | (* Note:
18 | Set $Context and $ContextPath to force symbols sent
19 | via WSTP to include their context. *)
20 | Block[{$Context = "Empty`", $ContextPath = {}},
21 | generateMessage[]
22 | ]
23 | ,
24 | Null
25 | ,
26 | {HoldForm[Message[MySymbol::msg, "a Rust LibraryLink function"]]}
27 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/Expressions.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | TestMatch[
4 | func = LibraryFunctionLoad[
5 | "libbasic_expressions",
6 | "echo_arguments",
7 | LinkObject,
8 | LinkObject
9 | ];
10 |
11 | func[2, 2]
12 | ,
13 | (* "finished echoing 2 argument(s)" *)
14 | (* FIXME: This output is a bug. Fix the bug and update this test case. *)
15 | Failure["RustPanic", <|
16 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
17 | "MessageParameters" -> <|
18 | "message" -> "evaluate(): evaluation of expression failed: WSTP error: symbol name 'ReturnPacket' has no context: \n\texpression: System`Echo[2]"
19 | |>,
20 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/src/lib.rs:"],
21 | "Backtrace" -> Missing["NotEnabled"]
22 | |>]
23 | ]
24 |
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/ManagedExpressions.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | TestMatch[
4 | loadFunctions = LibraryFunctionLoad[
5 | "libmanaged_exprs",
6 | "load_managed_exprs_functions",
7 | LinkObject,
8 | LinkObject
9 | ];
10 |
11 | $functions = loadFunctions["libmanaged_exprs"] // Sort
12 | ,
13 | <|
14 | "get_instance_data" -> Function[___],
15 | "set_instance_value" -> Function[___]
16 | |>
17 | ]
18 |
19 | Test[
20 | $obj = CreateManagedLibraryExpression["my_object", MyObject];
21 |
22 | MatchQ[$obj, MyObject[1]]
23 | ]
24 |
25 | Test[
26 | ManagedLibraryExpressionQ[$obj]
27 | ]
28 |
29 | Test[
30 | $objID = ManagedLibraryExpressionID[$obj];
31 |
32 | MatchQ[$objID, 1]
33 | ]
34 |
35 | Test[
36 | $functions["get_instance_data"][$objID]
37 | ,
38 | <| "Value" -> "default" |>
39 | ]
40 |
41 | Test[
42 | $functions["set_instance_value"][$objID, "new value"]
43 | ,
44 | Null
45 | ]
46 |
47 | Test[
48 | $functions["get_instance_data"][$objID]
49 | ,
50 | <| "Value" -> "new value" |>
51 | ]
52 |
53 | TestMatch[
54 | (* Clear $obj. This is the last copy of this managed expression, so the Kernel will
55 | call managed.rs/manage_instance() with a `ManagedExpressionEvent::Drop(_)` event.
56 |
57 | The fact that `ClearAll[..]` (or $obj going "out of scope" naturally) has the
58 | effect of calling back into the library to deallocate the object instance is the
59 | key feature of managed library expressions.
60 | *)
61 | ClearAll[$obj];
62 |
63 | $functions["get_instance_data"][$objID]
64 | ,
65 | (* Test that trying to access a deallocated instance fails. *)
66 | Failure["RustPanic", <|
67 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
68 | "MessageParameters" -> <|"message" -> "instance does not exist"|>,
69 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/exprs/managed.rs:"],
70 | "Backtrace" -> Missing["NotEnabled"]
71 | |>]
72 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/NumericArrays.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | Test[
4 | func = LibraryFunctionLoad[
5 | "libnumeric_arrays",
6 | "sum_int_numeric_array",
7 | {NumericArray},
8 | Integer
9 | ];
10 |
11 | {
12 | func[NumericArray[Range[10], "Integer64"]],
13 | func[NumericArray[Range[255], "UnsignedInteger8"]]
14 | }
15 | ,
16 | {
17 | 55,
18 | 32640
19 | }
20 | ]
21 |
22 | Test[
23 | func = LibraryFunctionLoad[
24 | "libnumeric_arrays",
25 | "sum_real_numeric_array",
26 | {NumericArray},
27 | Real
28 | ];
29 |
30 | {
31 | func[NumericArray[Range[1, 10, 1/81], "Real32"]],
32 | func[NumericArray[Range[1, 10, 1/81], "Real64"]]
33 | }
34 | ,
35 | {
36 | 4015.0,
37 | 4015.0
38 | }
39 | ]
40 |
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/RawFunctions.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | (*======================================*)
4 | (* Raw WSTP Functions *)
5 | (*======================================*)
6 |
7 | Test[
8 | func = LibraryFunctionLoad[
9 | "libraw_wstp_function",
10 | "demo_wstp_function",
11 | LinkObject,
12 | LinkObject
13 | ];
14 |
15 | func[2, 2]
16 | ,
17 | 4
18 | ]
19 |
20 | Test[
21 | func = LibraryFunctionLoad[
22 | "libraw_wstp_function",
23 | "demo_wstp_function_callback",
24 | LinkObject,
25 | LinkObject
26 | ];
27 |
28 | func[]
29 | ,
30 | "returned normally"
31 | ]
32 |
33 | Test[
34 | func = LibraryFunctionLoad[
35 | "libraw_wstp_function",
36 | "wstp_expr_function",
37 | LinkObject,
38 | LinkObject
39 | ];
40 |
41 | func[{1, 2, 3}]
42 | ,
43 | (* FIXME: This output is a bug. Fix the bug and update this test case. *)
44 | Failure["WSTP Error", <|
45 | "Message" -> "WSTP error: symbol name 'List' has no context"
46 | |>]
47 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Examples/WSTP.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | (* Test the loader function generated by `generate_loader!`. *)
4 | TestMatch[
5 | load = LibraryFunctionLoad[
6 | "libwstp_example",
7 | "load_wstp_functions",
8 | LinkObject,
9 | LinkObject
10 | ];
11 |
12 | $functions = load["libwstp_example"];
13 |
14 | Sort[$functions]
15 | ,
16 | (*
17 | These functions will typically have a structure similar to the following:
18 |
19 | Function[
20 | Block[{$Context = "RustLinkWSTPPrivateContext`", $ContextPath = {}},
21 | LibraryFunction["/path/to/libwstp_example.dylib", "count_args", LinkObject][
22 | ##
23 | ]
24 | ]
25 | ]
26 |
27 | Setting $Context and $ContextPath before each call to the WSTP library
28 | function is necessary to force all symbols sent across the link to be
29 | formatted with their context name included. See the 'Symbol contexts problem'
30 | section in the crate documentation for more information.
31 | *)
32 | <|
33 | "count_args" -> Function[__],
34 | "expr_string_join" -> Function[__],
35 | "link_expr_identity" -> Function[__],
36 | "square_wstp" -> Function[__],
37 | "string_join" -> Function[__],
38 | "total" -> Function[__],
39 | "total_args_i64" -> Function[__]
40 | |>
41 | ]
42 |
43 | Test[
44 | $functions["square_wstp"][4]
45 | ,
46 | 16
47 | ]
48 |
49 | (* Test that passing more than one argument to square_wstp() results in a Failure. *)
50 | TestMatch[
51 | $functions["square_wstp"][4, 4]
52 | ,
53 | Failure["RustPanic", <|
54 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
55 | "MessageParameters" -> <|"message" -> "square_wstp: expected to get a single argument"|>,
56 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/wstp.rs:"],
57 | "Backtrace" -> Missing["NotEnabled"]
58 | |>]
59 | ]
60 |
61 | Test[
62 | $functions["count_args"][a, b, c]
63 | ,
64 | 3
65 | ]
66 |
67 | Test[
68 | totalArgsI64 = $functions["total_args_i64"];
69 |
70 | {
71 | totalArgsI64[2, 2],
72 | totalArgsI64[1, 2, 3]
73 | }
74 | ,
75 | {
76 | 4,
77 | 6
78 | }
79 | ]
80 |
81 | Test[
82 | stringJoin = $functions["string_join"];
83 |
84 | {
85 | stringJoin["Hello, ", "World!"],
86 | stringJoin[Sequence @@ CharacterRange["A", "G"]],
87 | stringJoin[]
88 | },
89 | {
90 | "Hello, World!",
91 | "ABCDEFG",
92 | ""
93 | }
94 | ]
95 |
96 | TestMatch[
97 | linkExprIdentity = $functions["link_expr_identity"];
98 |
99 | linkExprIdentity[foo[], bar[baz]]
100 | ,
101 | {foo[], bar[baz]}
102 | ]
103 |
104 | TestMatch[
105 | exprStringJoin = $functions["expr_string_join"];
106 |
107 | {
108 | exprStringJoin[],
109 | exprStringJoin["Foo"],
110 | exprStringJoin["Foo", "Bar"],
111 | exprStringJoin[Sequence @@ CharacterRange["a", "f"]],
112 | exprStringJoin[1, 2, 3]
113 | }
114 | ,
115 | {
116 | "",
117 | "Foo",
118 | "FooBar",
119 | "abcdef",
120 | Failure["RustPanic", <|
121 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
122 | "MessageParameters" -> <|"message" -> "expected String argument, got: 1"|>,
123 | (* Avoid hard-coding the panic line/column number into the test. *)
124 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/wstp.rs:"],
125 | "Backtrace" -> Missing["NotEnabled"]
126 | |>]
127 | }
128 | ]
129 |
130 | TestMatch[
131 | total = $functions["total"];
132 |
133 | {
134 | total[],
135 | total[1, 2, 3],
136 | total[1, 2.5, 7],
137 | (* Cause an integer overflow. *)
138 | total[2^62, 2^62, 2^62],
139 | total[5, "Hello"]
140 | }
141 | ,
142 | {
143 | 0,
144 | 6,
145 | 10.5,
146 | Failure["RustPanic", <|
147 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
148 | "MessageParameters" -> <|"message" -> "attempt to add with overflow"|>,
149 | "SourceLocation" -> s0_?StringQ /; StringStartsQ[s0, "wolfram-library-link/examples/wstp.rs:"],
150 | "Backtrace" -> Missing["NotEnabled"]
151 | |>],
152 | Failure["RustPanic", <|
153 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
154 | "MessageParameters" -> <|
155 | "message" -> "expected argument at position 2 to be a number, got \"Hello\""
156 | |>,
157 | "SourceLocation" -> s1_?StringQ /; StringStartsQ[s1, "wolfram-library-link/examples/wstp.rs:"],
158 | "Backtrace" -> Missing["NotEnabled"]
159 | |>]
160 | }
161 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/LibraryResources/.include_in_git:
--------------------------------------------------------------------------------
1 | # This file exists because otherwise empty directories can't be saved to git.
2 |
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/PacletInfo.m:
--------------------------------------------------------------------------------
1 | (* Paclet Info File *)
2 |
3 | (* created 2020/05/04*)
4 |
5 | PacletObject[<|
6 | "Name" -> "RustLink",
7 | "Version" -> "0.0.1",
8 | "MathematicaVersion" -> "12.3+",
9 | "Extensions" -> {
10 | {"Documentation", Language -> "English"},
11 | {"LibraryLink"},
12 | {"Tests", "Method" -> "Experimental-v1"},
13 | {"Tests", "Method" -> "Experimental-v1", "Root" -> "Examples"},
14 |
15 | (* Test the documentation examples used in wolfram_library_link::docs. *)
16 | {"Tests", "Method" -> "Experimental-v1", "Root" -> "Examples/Docs/Convert"},
17 | {"Tests", "Method" -> "Experimental-v1", "Root" -> "Examples/Docs/EvaluateWolframCodeFromRust"}
18 | }
19 | |>]
20 |
21 |
22 |
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Tests/DataStore.wlt:
--------------------------------------------------------------------------------
1 |
2 | Needs["MUnit`"]
3 |
4 | Test[
5 | func = LibraryFunctionLoad[
6 | "liblibrary_tests",
7 | "test_empty_data_store",
8 | {},
9 | "DataStore"
10 | ];
11 |
12 | func[]
13 | ,
14 | Developer`DataStore[]
15 | ]
16 |
17 | Test[
18 | func = LibraryFunctionLoad[
19 | "liblibrary_tests",
20 | "test_single_int_data_store",
21 | {},
22 | "DataStore"
23 | ];
24 |
25 | func[]
26 | ,
27 | Developer`DataStore[1]
28 | ]
29 |
30 | Test[
31 | func = LibraryFunctionLoad[
32 | "liblibrary_tests",
33 | "test_multiple_int_data_store",
34 | {},
35 | "DataStore"
36 | ];
37 |
38 | func[]
39 | ,
40 | Developer`DataStore[1, 2, 3]
41 | ]
42 |
43 | Test[
44 | func = LibraryFunctionLoad[
45 | "liblibrary_tests",
46 | "test_unnamed_heterogenous_data_store",
47 | {},
48 | "DataStore"
49 | ];
50 |
51 | func[]
52 | ,
53 | Developer`DataStore[1, 2.0, "hello"]
54 | ]
55 |
56 | Test[
57 | func = LibraryFunctionLoad[
58 | "liblibrary_tests",
59 | "test_named_heterogenous_data_store",
60 | {},
61 | "DataStore"
62 | ];
63 |
64 | func[]
65 | ,
66 | Developer`DataStore[
67 | "an i64" -> 1,
68 | "an f64" -> 2.0,
69 | "a str" -> "hello"
70 | ]
71 | ]
72 |
73 | Test[
74 | func = LibraryFunctionLoad[
75 | "liblibrary_tests",
76 | "test_named_and_unnamed_heterogenous_data_store",
77 | {},
78 | "DataStore"
79 | ];
80 |
81 | func[]
82 | ,
83 | Developer`DataStore[1, "real" -> 2.0, "hello" -> "world"]
84 | ]
85 |
86 | (*====================================*)
87 | (* Non-atomic types *)
88 | (*====================================*)
89 |
90 | Test[
91 | func = LibraryFunctionLoad[
92 | "liblibrary_tests",
93 | "test_named_numeric_array_data_store",
94 | {},
95 | "DataStore"
96 | ];
97 |
98 | func[]
99 | ,
100 | Developer`DataStore[
101 | "array" -> NumericArray[{1, 2, 3}, "Integer64"]
102 | ]
103 | ]
104 |
105 | Test[
106 | func = LibraryFunctionLoad[
107 | "liblibrary_tests",
108 | "test_nested_data_store",
109 | {},
110 | "DataStore"
111 | ];
112 |
113 | func[]
114 | ,
115 | Developer`DataStore[
116 | "is_inner" -> False,
117 | Developer`DataStore["is_inner" -> True]
118 | ]
119 | ]
120 |
121 | Test[
122 | func = LibraryFunctionLoad[
123 | "liblibrary_tests",
124 | "test_iterated_nested_data_store",
125 | {},
126 | "DataStore"
127 | ];
128 |
129 | func[]
130 | ,
131 | Developer`DataStore[
132 | Developer`DataStore[
133 | Developer`DataStore[
134 | Developer`DataStore["level" -> 0],
135 | "level" -> 1
136 | ],
137 | "level" -> 2
138 | ]
139 | ]
140 | ]
141 |
142 | (*====================================*)
143 | (* DataStore arguments *)
144 | (*====================================*)
145 |
146 | Test[
147 | LibraryFunctionLoad[
148 | "liblibrary_tests",
149 | "test_data_store_arg",
150 | {"DataStore"},
151 | Integer
152 | ][
153 | Developer`DataStore["a", "b", "c"]
154 | ]
155 | ,
156 | 3
157 | ]
158 |
159 | (*====================================*)
160 | (* DataStore nodes *)
161 | (*====================================*)
162 |
163 | Test[
164 | LibraryFunctionLoad[
165 | "liblibrary_tests",
166 | "test_data_store_nodes",
167 | {},
168 | "Void"
169 | ][]
170 | ,
171 | Null
172 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Tests/Images.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | Test[
4 | LibraryFunctionLoad[
5 | "liblibrary_tests",
6 | "test_image_arg",
7 | {LibraryDataType[Image, "Bit"]},
8 | NumericArray
9 | ][
10 | Image[{{0, 1}, {1, 0}}, "Bit"]
11 | ]
12 | ,
13 | NumericArray[{0, 1, 1, 0}, "Integer8"]
14 | ]
15 |
16 | Test[
17 | LibraryFunctionLoad["liblibrary_tests", "test_create_bitmap_image", {}, Image][]
18 | ,
19 | Image[{{0, 1}, {1, 0}}, "Bit"]
20 | ]
21 |
22 | Test[
23 | LibraryFunctionLoad["liblibrary_tests", "test_create_color_rgb_u8_image", {}, Image][]
24 | ,
25 | Image[
26 | NumericArray[
27 | {
28 | {{255, 0 }, { 0, 200}}, (* Red channel*)
29 | {{0, 255}, { 0, 200}}, (* Green channel *)
30 | {{0, 0}, {255, 200}} (* Blue channel *)
31 | },
32 | "UnsignedInteger8"
33 | ],
34 | "Byte",
35 | ColorSpace -> "RGB",
36 | Interleaving -> False
37 | ]
38 | ]
39 |
40 | Test[
41 | LibraryFunctionLoad["liblibrary_tests", "test_create_color_rgb_f32_image", {}, Image][]
42 | ,
43 | Image[
44 | NumericArray[
45 | {
46 | {{1.0, 0.0}, {0.0, 0.8}}, (* Red channel*)
47 | {{0.0, 1.0}, {0.0, 0.8}}, (* Green channel *)
48 | {{0.0, 0.0}, {1.0, 0.8}} (* Blue channel *)
49 | },
50 | "Real32"
51 | ],
52 | "Real32",
53 | ColorSpace -> "RGB",
54 | Interleaving -> False
55 | ]
56 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Tests/NativeArgs.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | Test[
4 | LibraryFunctionLoad[
5 | "liblibrary_tests",
6 | "test_no_args",
7 | {},
8 | Integer
9 | ][]
10 | ,
11 | 4
12 | ]
13 |
14 | Test[
15 | LibraryFunctionLoad[
16 | "liblibrary_tests",
17 | "test_ret_void",
18 | {},
19 | "Void"
20 | ][]
21 | ,
22 | Null
23 | ]
24 |
25 | Test[
26 | LibraryFunctionLoad[
27 | "liblibrary_tests",
28 | "test_mint",
29 | {Integer},
30 | Integer
31 | ][5]
32 | ,
33 | 25
34 | ]
35 |
36 | Test[
37 | LibraryFunctionLoad[
38 | "liblibrary_tests",
39 | "test_raw_mint",
40 | {Integer},
41 | Integer
42 | ][9]
43 | ,
44 | 81
45 | ]
46 |
47 | Test[
48 | LibraryFunctionLoad[
49 | "liblibrary_tests",
50 | "test_mint_mint",
51 | {Integer, Integer},
52 | Integer
53 | ][5, 10]
54 | ,
55 | 15
56 | ]
57 |
58 | Test[
59 | LibraryFunctionLoad[
60 | "liblibrary_tests",
61 | "test_mreal",
62 | {Real},
63 | Real
64 | ][2.5]
65 | ,
66 | 6.25
67 | ]
68 |
69 | Test[
70 | LibraryFunctionLoad[
71 | "liblibrary_tests",
72 | "test_i64",
73 | {Integer},
74 | Integer
75 | ][5]
76 | ,
77 | 25
78 | ]
79 |
80 | Test[
81 | LibraryFunctionLoad[
82 | "liblibrary_tests",
83 | "test_i64_i64",
84 | {Integer, Integer},
85 | Integer
86 | ][5, 10]
87 | ,
88 | 15
89 | ]
90 |
91 | Test[
92 | LibraryFunctionLoad[
93 | "liblibrary_tests",
94 | "test_f64",
95 | {Real},
96 | Real
97 | ][2.5]
98 | ,
99 | 6.25
100 | ]
101 |
102 | (*---------*)
103 | (* Strings *)
104 | (*---------*)
105 |
106 | (* Test[
107 | LibraryFunctionLoad[
108 | "liblibrary_tests",
109 | "test_str",
110 | {String},
111 | String
112 | ]["hello"]
113 | ,
114 | "olleh"
115 | ] *)
116 |
117 | Test[
118 | LibraryFunctionLoad[
119 | "liblibrary_tests",
120 | "test_string",
121 | {String},
122 | String
123 | ]["hello"]
124 | ,
125 | "olleh"
126 | ]
127 |
128 | Test[
129 | LibraryFunctionLoad[
130 | "liblibrary_tests",
131 | "test_c_string",
132 | {String},
133 | Integer
134 | ]["hello world"]
135 | ,
136 | 11
137 | ]
138 |
139 | (*---------*)
140 | (* Panics *)
141 | (*---------*)
142 |
143 | Test[
144 | LibraryFunctionLoad[
145 | "liblibrary_tests",
146 | "test_panic",
147 | {},
148 | "Void"
149 | ][]
150 | ,
151 | LibraryFunctionError["LIBRARY_USER_ERROR", 1002]
152 | ,
153 | {LibraryFunction::rterr}
154 | ]
155 |
156 | (*----------------*)
157 | (* NumericArray's *)
158 | (*----------------*)
159 |
160 | Test[
161 | totalI64 = LibraryFunctionLoad[
162 | "liblibrary_tests",
163 | "total_i64",
164 | {LibraryDataType[NumericArray, "Integer64"]},
165 | Integer
166 | ];
167 |
168 | totalI64[NumericArray[Range[100], "Integer64"]]
169 | ,
170 | 5050
171 | ]
172 |
173 | Test[
174 | positiveQ = LibraryFunctionLoad[
175 | "liblibrary_tests",
176 | "positive_i64",
177 | {LibraryDataType[NumericArray, "Integer64"]},
178 | LibraryDataType[NumericArray, "UnsignedInteger8"]
179 | ];
180 |
181 | positiveQ[NumericArray[{0, 1, -2, 3, 4, -5}, "Integer64"]]
182 | ,
183 | NumericArray[{0, 1, 0, 1, 1, 0}, "UnsignedInteger8"]
184 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Tests/NumericArrayConversions.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | Test[
4 | LibraryFunctionLoad["liblibrary_tests", "test_na_conversions", {}, "Void"][]
5 | ,
6 | Null
7 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Tests/ShareCounts.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | $NAType = LibraryDataType[NumericArray, "Integer64"]
4 | (* Constructs a fresh NumericArray on each access. *)
5 | $NA := NumericArray[{1, 2, 3}, "Integer64"]
6 |
7 | Test[
8 | LibraryFunctionLoad[
9 | "liblibrary_tests",
10 | "test_na_automatic_count",
11 | {
12 | {LibraryDataType[NumericArray, "Integer64"], Automatic}
13 | },
14 | Integer
15 | ][$NA]
16 | ,
17 | 0
18 | ]
19 |
20 | Test[
21 | LibraryFunctionLoad[
22 | "liblibrary_tests",
23 | "test_na_constant_count",
24 | {
25 | {LibraryDataType[NumericArray, "Integer64"], "Constant"}
26 | },
27 | Integer
28 | ][$NA]
29 | ,
30 | 0
31 | ]
32 |
33 | Test[
34 | LibraryFunctionLoad[
35 | "liblibrary_tests",
36 | "test_na_manual_count",
37 | {
38 | {LibraryDataType[NumericArray, "Integer64"], "Manual"}
39 | },
40 | Integer
41 | ][$NA]
42 | ,
43 | 0
44 | ]
45 |
46 | Test[
47 | LibraryFunctionLoad[
48 | "liblibrary_tests",
49 | "test_na_shared_count",
50 | {
51 | {LibraryDataType[NumericArray, "Integer64"], "Shared"}
52 | },
53 | Integer
54 | ][$NA]
55 | ,
56 | 1
57 | ]
58 |
59 | (* Test passing one NumericArray as two different arguments, using "Constant". *)
60 | Test[
61 | With[{array = $NA},
62 | LibraryFunctionLoad[
63 | "liblibrary_tests",
64 | "test_na_constant_are_ptr_eq",
65 | {
66 | {LibraryDataType[NumericArray, "Integer64"], "Constant"},
67 | {LibraryDataType[NumericArray, "Integer64"], "Constant"}
68 | },
69 | "DataStore"
70 | ][array, array]
71 | ]
72 | ,
73 | (* The two arrays:
74 | * should be `ptr_eq()`
75 | * their `share_count()` should be 0
76 | *)
77 | Developer`DataStore[True, 0]
78 | ]
79 |
80 | (* Test passing one NumericArray as two different arguments, using "Manual". *)
81 | Test[
82 | With[{array = $NA},
83 | LibraryFunctionLoad[
84 | "liblibrary_tests",
85 | "test_na_manual_are_not_ptr_eq",
86 | {
87 | {LibraryDataType[NumericArray, "Integer64"], "Manual"},
88 | {LibraryDataType[NumericArray, "Integer64"], "Manual"}
89 | },
90 | "DataStore"
91 | ][array, array]
92 | ]
93 | ,
94 | (* The two arrays:
95 | * should *not* be `ptr_eq()`
96 | * their `share_count()` should be 0
97 | * `array1.as_slice_mut().is_some()` should be True
98 | *)
99 | Developer`DataStore[False, 0, True]
100 | ]
101 |
102 | (* Test passing one NumericArray as two different arguments, using "Shared". *)
103 | Test[
104 | With[{array = $NA},
105 | LibraryFunctionLoad[
106 | "liblibrary_tests",
107 | "test_na_shared_are_ptr_eq",
108 | {
109 | {LibraryDataType[NumericArray, "Integer64"], "Shared"},
110 | {LibraryDataType[NumericArray, "Integer64"], "Shared"}
111 | },
112 | "DataStore"
113 | ][array, array]
114 | ]
115 | ,
116 | (* The two arrays:
117 | * should be `ptr_eq()`
118 | * their `share_count()` should be 2
119 | * `array1.as_slice_mut().is_some()` should be False
120 | *)
121 | Developer`DataStore[True, 2, False]
122 | ]
123 |
124 | (* Test cloning a NumericArray *)
125 | Test[
126 | LibraryFunctionLoad[
127 | "liblibrary_tests",
128 | "test_na_clone",
129 | {},
130 | "Boolean"
131 | ][]
132 | ]
133 |
134 | (* Test cloning a "Shared" NumericArray *)
135 | Test[
136 | LibraryFunctionLoad[
137 | "liblibrary_tests",
138 | "test_na_shared_clone",
139 | {
140 | {LibraryDataType[NumericArray, "Integer64"], "Shared"}
141 | },
142 | "Boolean"
143 | ][$NA]
144 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Tests/Threading.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | Test[
4 | Block[{$Context = "UnlikelyContext`", $ContextPath = {}},
5 | LibraryFunctionLoad[
6 | "liblibrary_tests", "test_runtime_function_from_main_thread", {}, "Boolean"
7 | ][]
8 | ]
9 | ,
10 | True
11 | ]
12 |
13 | Test[
14 | result = Block[{$Context = "UnlikelyContext`", $ContextPath = {}},
15 | LibraryFunctionLoad[
16 | "liblibrary_tests", "test_runtime_function_from_non_main_thread", {}, String
17 | ][]
18 | ];
19 |
20 | StringQ[result] && StringStartsQ[
21 | result,
22 | "PANIC: error: attempted to call back into the Wolfram Kernel from a non-main thread at"
23 | ]
24 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/RustLink/Tests/WSTP.wlt:
--------------------------------------------------------------------------------
1 | Needs["MUnit`"]
2 |
3 | TestMatch[
4 | LibraryFunctionLoad[
5 | "liblibrary_tests",
6 | "test_wstp_fn_empty",
7 | LinkObject,
8 | LinkObject
9 | ][]
10 | ,
11 | (* The empty arguments list is never read, so it's left on the link and assumed to be
12 | the return value. *)
13 | {}
14 | ]
15 |
16 | TestMatch[
17 | LibraryFunctionLoad[
18 | "liblibrary_tests",
19 | "test_wstp_fn_panic_immediately",
20 | LinkObject,
21 | LinkObject
22 | ][]
23 | ,
24 | Failure["RustPanic", <|
25 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
26 | "MessageParameters" -> <|"message" -> "successful panic"|>,
27 | (* Avoid hard-coding the panic line/column number into the test. *)
28 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"],
29 | "Backtrace" -> Missing["NotEnabled"]
30 | |>]
31 | ]
32 |
33 | TestMatch[
34 | LibraryFunctionLoad[
35 | "liblibrary_tests",
36 | "test_wstp_fn_panic_immediately_with_formatting",
37 | LinkObject,
38 | LinkObject
39 | ][]
40 | ,
41 | Failure["RustPanic", <|
42 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
43 | "MessageParameters" -> <|"message" -> "successful formatted panic"|>,
44 | (* Avoid hard-coding the panic line/column number into the test. *)
45 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"],
46 | "Backtrace" -> Missing["NotEnabled"]
47 | |>]
48 | ]
49 |
50 | TestMatch[
51 | LibraryFunctionLoad[
52 | "liblibrary_tests",
53 | "test_wstp_fn_panic_partial_result",
54 | LinkObject,
55 | LinkObject
56 | ][]
57 | ,
58 | Failure["RustPanic", <|
59 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
60 | "MessageParameters" -> <|"message" -> "incomplete result"|>,
61 | (* Avoid hard-coding the panic line/column number into the test. *)
62 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"],
63 | "Backtrace" -> Missing["NotEnabled"]
64 | |>]
65 | ]
66 |
67 | TestMatch[
68 | LibraryFunctionLoad[
69 | "liblibrary_tests",
70 | "test_wstp_fn_return_partial_result",
71 | LinkObject,
72 | LinkObject
73 | ][]
74 | ,
75 | Unevaluated @ LibraryFunction[
76 | s_String /; StringEndsQ[
77 | s,
78 | FileNameJoin[{"RustLink", "LibraryResources", $SystemID}]
79 | ~~ $PathnameSeparator
80 | ~~ RepeatedNull["lib", 1]
81 | ~~ "library_tests."
82 | ~~ ("dylib" | "dll" | "so")
83 | ]
84 | ,
85 | "test_wstp_fn_return_partial_result"
86 | ,
87 | LinkObject
88 | ][]
89 | ]
90 |
91 | TestMatch[
92 | LibraryFunctionLoad[
93 | "liblibrary_tests",
94 | "test_wstp_fn_poison_link_and_panic",
95 | LinkObject,
96 | LinkObject
97 | ][]
98 | ,
99 | Failure["RustPanic", <|
100 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
101 | "MessageParameters" -> <|"message" -> "successful panic"|>,
102 | (* Avoid hard-coding the panic line/column number into the test. *)
103 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"],
104 | "Backtrace" -> Missing["NotEnabled"]
105 | |>]
106 | ]
107 |
108 | TestMatch[
109 | LibraryFunctionLoad[
110 | "liblibrary_tests",
111 | "test_wstp_panic_with_empty_link",
112 | LinkObject,
113 | LinkObject
114 | ][]
115 | ,
116 | Failure["RustPanic", <|
117 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
118 | "MessageParameters" -> <|"message" -> "panic while !link.is_ready()"|>,
119 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"],
120 | "Backtrace" -> Missing["NotEnabled"]
121 | |>]
122 | ]
123 |
124 | (*====================================*)
125 | (* Vec *)
126 | (*====================================*)
127 |
128 | TestMatch[
129 | Block[{$Context = "UnusedContext`", $ContextPath = {}},
130 | LibraryFunctionLoad[
131 | "liblibrary_tests",
132 | "test_wstp_expr_return_null",
133 | LinkObject,
134 | LinkObject
135 | ][]
136 | ]
137 | ,
138 | Null
139 | ]
--------------------------------------------------------------------------------
/wolfram-library-link/docs/Overview.md:
--------------------------------------------------------------------------------
1 | # Functionality Overview
2 |
3 | ## Function Types
4 |
5 | #### Native functions
6 |
7 | #### WSTP functions
8 |
9 | #### TODO: Expr functions
10 |
11 | ## WSTP Functions
12 |
13 | ### Symbol contexts problem
14 |
15 |
16 |
17 | Background
18 |
19 |
20 | In the Wolfram Language, a symbol is made up of two parts: a context, and a symbol name.
21 | For example, in the the symbol `` System`Plot ``, `` System` `` is the context, and `Plot`
22 | is the symbol name. The context denotes a collection of related symbols. For example, all
23 | symbols that are part of the core Wolfram Language are in the ``"System`"`` context; when
24 | you start a new Wolfram Language session, the default context that new symbols get created
25 | in is called ``"Global`"``; and so on. However, you won't often see symbol contexts
26 | written out explicitly in a Wolfram Language program. Instead, when a symbol name is
27 | entered, the system looks up that symbol name in the contexts listed in the
28 | [`$ContextPath`][ref/$ContextPath]: if a symbol with that name exists in one of the
29 | listed contexts, then the symbol name the user entered resolves to that context.
30 |
31 | So, for example, if the user enters the symbol name `Plot`, and ``"System`"`` is on
32 | `$ContextPath`, the system deduces the user was referring to the symbol ``System`Plot``.
33 | In this way, `$ContextPath` allows the user to user shorter symbol names to refer to
34 | symbols, and avoid having to write out the full context + symbol name as input.
35 |
36 | This shortening also works when printing symbols. For example, doing `ToString[Plot]`
37 | doesn't return ``"System`Plot"``, but rather just `"Plot"`. And herein lies the problem
38 | for WSTP functions.
39 |
40 |
41 | When an expression is sent across a WSTP link, symbols whose contexts are equal to
42 | [`$Context`][ref/$Context] or on [`$ContextPath`][ref/$ContextPath] will be sent as
43 | strings without the symbol context.
44 |
45 | This is a problem because, within Rust code, a symbol name without a context is ambiguous.
46 | If Rust code is expecting to get the symbol ``MyPackage`foo``, but recieves just `foo`
47 | over the WSTP link, there is no easy way for it to tell if that `foo` came from the symbol
48 | it expected, or e.g. ``MyOtherPackage`foo``.
49 |
50 | #### The solution
51 |
52 | When calling a WSTP function that parses the incoming arguments using the
53 | [`Expr`][crate::expr::Expr] type in some way (e.g. by calling `Link::get_expr()`), use
54 | the following idiom:
55 |
56 | ```wolfram
57 | (* func: LibraryFunction[_, _, LinkObject, LinkObject] *)
58 |
59 | Block[{$Context = "UnusedContext`", $ContextPath = {}},
60 | func[arg1, arg2, ...]
61 | ]
62 | ```
63 |
64 | Setting `$Context` and `$ContextPath` in this way will force symbols written to the
65 | function `Link` object to explicitly include their context.
66 |
67 | ### Panic `Failure[..]`s
68 |
69 | When a WSTP function [panics][panics], a [`Failure["RustPanic", ...]`][ref/Failure] object
70 | will be returned to the Wolfram Language.
71 |
72 | [panics]: https://doc.rust-lang.org/std/macro.panic.html
73 | [ref/Failure]: https://reference.wolfram.com/language/ref/Failure.html
74 |
75 | ##### Example
76 |
77 | ```rust
78 | # mod scope {
79 | use wolfram_library_link::{self as wll, wstp::Link};
80 |
81 | #[wll::export(wstp)]
82 | fn sqrt(link: &mut Link) {
83 | let arg_count = link.test_head("List").unwrap();
84 |
85 | if arg_count != 1 {
86 | panic!("expected 1 argument, got {}", arg_count);
87 | }
88 |
89 | let arg = link.get_f64().expect("expected Real argument");
90 |
91 | if arg.is_negative() {
92 | panic!("cannot get the square root of a negative number");
93 | }
94 |
95 | let value = arg.sqrt();
96 |
97 | link.put_f64(value).unwrap()
98 | }
99 | # }
100 | ```
101 |
102 | ```wolfram
103 | sqrt = LibraryFunctionLoad["...", "sqrt", LinkObject, LinkObject];
104 |
105 | sqrt[]
106 |
107 | (* Returns:
108 | Failure["RustPanic", <|
109 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`",
110 | "MessageParameters" -> <| "message" -> "expected 1 argument, got 0" |>,
111 | "SourceLocation" -> "<...>",
112 | "Backtrace" -> Missing["NotEnabled"]
113 | >]
114 | *)
115 | ```
116 |
117 | #### Backtraces
118 |
119 | `wolfram-library-link` can optionally capture a stack backtrace for panics that occur
120 | within Rust LibraryLink functions. This behavior is disabled by default, but can be
121 | enabled by setting the `LIBRARY_LINK_RUST_BACKTRACE` environment variable to `"True"`:
122 |
123 | ```wolfram
124 | SetEnvironment["LIBRARY_LINK_RUST_BACKTRACE" -> "True"]
125 | ```
126 |
127 | ##### Note on panic hooks
128 |
129 | `wolfram-library-link` captures information about panics by setting a custom Rust
130 | [panic hook](https://doc.rust-lang.org/std/panic/fn.set_hook.html).
131 |
132 |
133 |
134 |
135 |
136 |
137 | [ref/$Context]: https://reference.wolfram.com/language/ref/$Context.html
138 | [ref/$ContextPath]: https://reference.wolfram.com/language/ref/$ContextPath.html
--------------------------------------------------------------------------------
/wolfram-library-link/docs/README.md:
--------------------------------------------------------------------------------
1 | The files in this directory are included directly in the `wolfram-library-link` crate
2 | documentation using:
3 |
4 | ```rust
5 | #[doc = include_str!("<..>/docs/included/.md")]
6 | ```
7 |
8 | When viewed as markdown files in an online repository file browser, intra-doc links will
9 | not work correctly.
--------------------------------------------------------------------------------
/wolfram-library-link/examples/aborts.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link as wll;
2 |
3 | /// This function will execute forever until a Wolfram Language abort occurs.
4 | #[wll::export]
5 | fn wait_for_abort() {
6 | loop {
7 | if wll::aborted() {
8 | return;
9 | }
10 |
11 | std::thread::yield_now();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/async/async_file_watcher.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | fs,
3 | path::PathBuf,
4 | time::{Duration, SystemTime},
5 | };
6 |
7 | use wolfram_library_link::{self as wll, sys::mint, AsyncTaskObject, DataStore};
8 |
9 | /// Start an asynchronous task that will watch for modifications to a file.
10 | ///
11 | /// See `RustLink/Tests/AsyncExamples.wlt` for example usage of this function.
12 | #[wll::export]
13 | fn start_file_watcher(pause_interval_ms: mint, path: String) -> mint {
14 | let pause_interval_ms =
15 | u64::try_from(pause_interval_ms).expect("mint interval overflows u64");
16 |
17 | let path = PathBuf::from(path);
18 |
19 | // Spawn a new thread, which will run in the background and check for file
20 | // modifications.
21 | let task = AsyncTaskObject::spawn_with_thread(move |task: AsyncTaskObject| {
22 | file_watch_thread_function(task, pause_interval_ms, &path);
23 | });
24 |
25 | task.id()
26 | }
27 |
28 | /// This function is called from the spawned background thread.
29 | fn file_watch_thread_function(
30 | task: AsyncTaskObject,
31 | pause_interval_ms: u64,
32 | path: &PathBuf,
33 | ) {
34 | let mut prev_changed: Option = fs::metadata(path)
35 | .and_then(|metadata| metadata.modified())
36 | .ok();
37 |
38 | // Stateful closure which checks if the file at `path` has been modified since the
39 | // last time this closure was called (and `prev_changed was updated). Using a closure
40 | // simplifies the control flow in the main `loop` below, which should sleep on every
41 | // iteration regardless of how this function returns.
42 | let mut check_for_modification = || -> Option<_> {
43 | let changed: Option = fs::metadata(path).ok();
44 |
45 | let notify: Option = match (&prev_changed, changed) {
46 | (Some(prev), Some(latest)) => {
47 | let latest: SystemTime = match latest.modified() {
48 | Ok(latest) => latest,
49 | Err(_) => return None,
50 | };
51 |
52 | if *prev != latest {
53 | prev_changed = Some(latest.clone());
54 | Some(latest)
55 | } else {
56 | None
57 | }
58 | },
59 | // TODO: Notify on file removal?
60 | (Some(_prev), None) => None,
61 | (None, Some(latest)) => latest.modified().ok(),
62 | (None, None) => None,
63 | };
64 |
65 | let time = notify?;
66 |
67 | let since_epoch = match time.duration_since(std::time::UNIX_EPOCH) {
68 | Ok(duration) => duration,
69 | Err(_) => return None,
70 | };
71 |
72 | let since_epoch = since_epoch.as_secs();
73 |
74 | Some(since_epoch)
75 | };
76 |
77 | loop {
78 | if !task.is_alive() {
79 | break;
80 | }
81 |
82 | // Check to see if the file has been modified. If it has, raise an async event
83 | // called "change", and attach the modification timestamp as event data.
84 | if let Some(modification) = check_for_modification() {
85 | let mut data = DataStore::new();
86 | data.add_i64(modification as i64);
87 |
88 | task.raise_async_event("change", data);
89 | }
90 |
91 | // Wait for a bit before polling again for any changes to the file.
92 | std::thread::sleep(Duration::from_millis(pause_interval_ms));
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/async/async_file_watcher_raw.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | ffi::CStr,
3 | fs,
4 | os::raw::{c_uint, c_void},
5 | path::PathBuf,
6 | time::{Duration, SystemTime},
7 | };
8 |
9 | use wolfram_library_link::{
10 | self as wll, rtl,
11 | sys::{self, mint, MArgument, LIBRARY_FUNCTION_ERROR, LIBRARY_NO_ERROR},
12 | };
13 |
14 | struct FileWatcherArgs {
15 | pause_interval_ms: u64,
16 | path: PathBuf,
17 | }
18 |
19 | /// Start an asynchronous task that will watch for modifications to a file.
20 | ///
21 | /// See `RustLink/Tests/AsyncExamples.wlt` for example usage of this function.
22 | #[no_mangle]
23 | pub extern "C" fn start_file_watcher(
24 | lib_data: sys::WolframLibraryData,
25 | arg_count: mint,
26 | args: *mut MArgument,
27 | res: MArgument,
28 | ) -> c_uint {
29 | let args = unsafe { std::slice::from_raw_parts(args, arg_count as usize) };
30 |
31 | if args.len() != 2 {
32 | return LIBRARY_FUNCTION_ERROR;
33 | }
34 |
35 | if unsafe { wll::initialize(lib_data) }.is_err() {
36 | return LIBRARY_FUNCTION_ERROR;
37 | }
38 |
39 | let task_arg = unsafe {
40 | FileWatcherArgs {
41 | pause_interval_ms: u64::try_from(*args[0].integer)
42 | .expect("i64 interval overflows u64"),
43 | path: {
44 | let cstr = CStr::from_ptr(*args[1].utf8string);
45 | match cstr.to_str() {
46 | Ok(s) => PathBuf::from(s),
47 | Err(_) => return LIBRARY_FUNCTION_ERROR,
48 | }
49 | },
50 | }
51 | };
52 |
53 | // FIXME: This box is being leaked. Where is an appropriate place to drop it?
54 | let task_arg = Box::into_raw(Box::new(task_arg)) as *mut c_void;
55 |
56 | // Spawn a new thread, which will run in the background and check for file
57 | // modifications.
58 | unsafe {
59 | let task_id: mint = wll::rtl::createAsynchronousTaskWithThread(
60 | Some(file_watch_thread_function),
61 | task_arg,
62 | );
63 | *res.integer = task_id;
64 | }
65 |
66 | LIBRARY_NO_ERROR
67 | }
68 |
69 | /// This function is called from the spawned background thread.
70 | extern "C" fn file_watch_thread_function(async_object_id: mint, task_arg: *mut c_void) {
71 | let task_arg = task_arg as *mut FileWatcherArgs;
72 | let task_arg: &FileWatcherArgs = unsafe { &*task_arg };
73 |
74 | let FileWatcherArgs {
75 | pause_interval_ms,
76 | ref path,
77 | } = *task_arg;
78 |
79 | let mut prev_changed: Option = fs::metadata(path)
80 | .and_then(|metadata| metadata.modified())
81 | .ok();
82 |
83 | // Stateful closure which checks if the file at `path` has been modified since the
84 | // last time this closure was called (and `prev_changed was updated). Using a closure
85 | // simplifies the control flow in the main `loop` below, which should sleep on every
86 | // iteration regardless of how this function returns.
87 | let mut check_for_modification = || -> Option<_> {
88 | let changed: Option = fs::metadata(path).ok();
89 |
90 | let notify: Option = match (&prev_changed, changed) {
91 | (Some(prev), Some(latest)) => {
92 | let latest: SystemTime = match latest.modified() {
93 | Ok(latest) => latest,
94 | Err(_) => return None,
95 | };
96 |
97 | if *prev != latest {
98 | prev_changed = Some(latest.clone());
99 | Some(latest)
100 | } else {
101 | None
102 | }
103 | },
104 | // TODO: Notify on file removal?
105 | (Some(_prev), None) => None,
106 | (None, Some(latest)) => latest.modified().ok(),
107 | (None, None) => None,
108 | };
109 |
110 | let time = notify?;
111 |
112 | let since_epoch = match time.duration_since(std::time::UNIX_EPOCH) {
113 | Ok(duration) => duration,
114 | Err(_) => return None,
115 | };
116 |
117 | let since_epoch = since_epoch.as_secs();
118 |
119 | Some(since_epoch)
120 | };
121 |
122 | loop {
123 | if unsafe { rtl::asynchronousTaskAliveQ(async_object_id) } == 0 {
124 | break;
125 | }
126 |
127 | // Check to see if the file has been modified. If it has, raise an async event
128 | // called "change", and attach the modification timestamp as event data.
129 | if let Some(modification) = check_for_modification() {
130 | unsafe {
131 | let data_store: sys::DataStore = rtl::createDataStore();
132 | rtl::DataStore_addInteger(data_store, modification as i64);
133 |
134 | rtl::raiseAsyncEvent(
135 | async_object_id,
136 | "change\0".as_ptr() as *mut _,
137 | data_store,
138 | )
139 | }
140 | }
141 |
142 | // Wait for a bit before polling again for any changes to the file.
143 | std::thread::sleep(Duration::from_millis(pause_interval_ms));
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/basic_types.rs:
--------------------------------------------------------------------------------
1 | //! This example demonstrates how LibraryLink native data types can be used in Rust
2 | //! functions called via LibraryLink.
3 |
4 | use wolfram_library_link::{self as wll, NumericArray, UninitNumericArray};
5 |
6 | wll::generate_loader!(load_basic_types_functions);
7 |
8 | //======================================
9 | // Primitive data types
10 | //======================================
11 |
12 | //---------
13 | // square()
14 | //---------
15 |
16 | /// Define a function to square a number.
17 | ///
18 | /// The exported LibraryLink function may be loaded and used by evaluating:
19 | ///
20 | /// ```wolfram
21 | /// square = LibraryFunctionLoad["libbasic_types", "square", {Integer}, Integer];
22 | /// square[2]
23 | /// ```
24 | //
25 | // Export the `square` function via LibraryLink. This will generate a "wrapper" function
26 | // that correctly implements the lightweight LibraryLink <=> Rust conversion.
27 | #[wll::export]
28 | fn square(x: i64) -> i64 {
29 | x * x
30 | }
31 |
32 | //-----------------
33 | // reverse_string()
34 | //-----------------
35 |
36 | #[wll::export]
37 | fn reverse_string(string: String) -> String {
38 | string.chars().rev().collect()
39 | }
40 |
41 | //------------------
42 | // add2() and add3()
43 | //------------------
44 |
45 | #[wll::export]
46 | fn add2(x: i64, y: i64) -> i64 {
47 | x + y
48 | }
49 |
50 | #[wll::export]
51 | fn add3(x: i64, y: i64, z: i64) -> i64 {
52 | x + y + z
53 | }
54 |
55 | //======================================
56 | // NumericArray's
57 | //======================================
58 |
59 | //------------
60 | // total_i64()
61 | //------------
62 |
63 | // Load and use by evaluating:
64 | //
65 | // ```wolfram
66 | // total = LibraryFunctionLoad[
67 | // "libbasic_types",
68 | // "total_i64",
69 | // {LibraryDataType[NumericArray, "Integer64"]},
70 | // Integer
71 | // ];
72 | //
73 | // total[NumericArray[Range[100], "Integer64"]]
74 | // ```
75 | #[wll::export]
76 | fn total_i64(list: &NumericArray) -> i64 {
77 | list.as_slice().into_iter().sum()
78 | }
79 |
80 | //---------------
81 | // positive_i64()
82 | //---------------
83 |
84 | /// Get the sign of every element in `list` as a numeric array of 0's and 1's.
85 | ///
86 | /// The returned array will have the same dimensions as `list`.
87 | #[wll::export]
88 | fn positive_i64(list: &NumericArray) -> NumericArray {
89 | let mut bools: UninitNumericArray =
90 | UninitNumericArray::from_dimensions(list.dimensions());
91 |
92 | for pair in list.as_slice().into_iter().zip(bools.as_slice_mut()) {
93 | let (elem, entry): (&i64, &mut std::mem::MaybeUninit) = pair;
94 |
95 | entry.write(u8::from(elem.is_positive()));
96 | }
97 |
98 | unsafe { bools.assume_init() }
99 | }
100 |
101 | //======================================
102 | // get_random_number()
103 | //======================================
104 |
105 | // Load and use by evaluating:
106 | //
107 | // ```wolfram
108 | // randomNumber = LibraryFunctionLoad[
109 | // "libbasic_types",
110 | // "xkcd_get_random_number",
111 | // {},
112 | // Integer
113 | // ];
114 | // randomNumber[]
115 | // ```
116 | #[wll::export(name = "xkcd_get_random_number")]
117 | fn get_random_number() -> i64 {
118 | // chosen by fair dice roll.
119 | // guaranteed to be random.
120 | // xkcd.com/221
121 | 4
122 | }
123 |
124 | //======================================
125 | // raw_square()
126 | //======================================
127 |
128 | #[wll::export]
129 | fn raw_square(args: &[wll::sys::MArgument], ret: wll::sys::MArgument) {
130 | if args.len() != 1 {
131 | panic!("unexpected number of arguments");
132 | }
133 |
134 | let x: i64 = unsafe { *args[0].integer };
135 |
136 | unsafe {
137 | *ret.integer = x * x;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/data_store.rs:
--------------------------------------------------------------------------------
1 | //! This example demonstrates how the *LibraryLink* "DataStore" type can be used to pass
2 | //! expression-like data structures to and from Rust.
3 | //!
4 | //! The "DataStore" can be manipulated natively from Rust code, and is serialized to
5 | //! an expression structure that looks like:
6 | //!
7 | //! ```wolfram
8 | //! Developer`DataStore[1, "Hello, World!", "key" -> -12.5]
9 | //! ```
10 | //!
11 | //! A "DataStore" contains a sequence of values, which may differ in type, and each
12 | //! value may optionally be named.
13 |
14 | use wolfram_library_link::{self as wll, DataStore, DataStoreNodeValue};
15 |
16 | //--------------
17 | // string_join()
18 | //--------------
19 |
20 | /// Join the strings in a `DataStore`.
21 | ///
22 | /// This function may be called by evaluating:
23 | ///
24 | /// ```wolfram
25 | /// stringJoin = LibraryFunctionLoad["libdata_store", "string_join", {"DataStore"}, "String"];
26 | ///
27 | /// (* Evaluates to: "hello world" *)
28 | /// stringJoin[Developer`DataStore["hello", " ", "world"]]
29 | /// ```
30 | #[wll::export]
31 | fn string_join(store: DataStore) -> String {
32 | let mut buffer = String::new();
33 |
34 | for node in store.nodes() {
35 | // If `node.value()` is a string, append it to our string.
36 | // If `node.value()` is NOT a string, silently skip it.
37 | if let DataStoreNodeValue::Str(string) = node.value() {
38 | buffer.push_str(string);
39 | }
40 | }
41 |
42 | buffer
43 | }
44 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/docs/README.md:
--------------------------------------------------------------------------------
1 | Examples from the `wolfram_library_link::docs` module.
2 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/docs/convert/manual_wstp.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link::{
2 | export,
3 | wstp::{self, Link},
4 | };
5 |
6 | struct Point {
7 | x: f64,
8 | y: f64,
9 | }
10 |
11 | #[export(wstp)]
12 | fn create_point(link: &mut Link) {
13 | // Assert that no arguments were given.
14 | assert_eq!(link.test_head("List"), Ok(0));
15 |
16 | let point = Point { x: 1.0, y: 2.0 };
17 |
18 | point.put(link).unwrap();
19 | }
20 |
21 | impl Point {
22 | fn put(&self, link: &mut Link) -> Result<(), wstp::Error> {
23 | let Point { x, y } = *self;
24 |
25 | // Point[{x, y}]
26 | link.put_function("System`Point", 1)?;
27 | link.put_function("System`List", 2)?;
28 | link.put_f64(x)?;
29 | link.put_f64(y)?;
30 |
31 | Ok(())
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/docs/convert/using_expr.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link::{
2 | export,
3 | expr::{Expr, Symbol},
4 | };
5 |
6 | struct Point {
7 | x: f64,
8 | y: f64,
9 | }
10 |
11 | #[export(wstp)]
12 | fn create_point2(args: Vec) -> Expr {
13 | assert!(args.is_empty());
14 |
15 | let point = Point { x: 3.0, y: 4.0 };
16 |
17 | point.to_expr()
18 | }
19 |
20 | impl Point {
21 | fn to_expr(&self) -> Expr {
22 | let Point { x, y } = *self;
23 |
24 | Expr::normal(Symbol::new("System`Point"), vec![Expr::list(vec![
25 | Expr::real(x),
26 | Expr::real(y),
27 | ])])
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/docs/evaluate_wolfram_code_from_rust/generate_message.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link::{
2 | self as wll, export,
3 | expr::{Expr, Symbol},
4 | };
5 |
6 | #[export(wstp)]
7 | fn generate_message(_: Vec) {
8 | // Construct the expression `Message[MySymbol::msg, "..."]`.
9 | let message = Expr::normal(Symbol::new("System`Message"), vec![
10 | // MySymbol::msg is MessageName[MySymbol, "msg"]
11 | Expr::normal(Symbol::new("System`MessageName"), vec![
12 | Expr::from(Symbol::new("Global`MySymbol")),
13 | Expr::string("msg"),
14 | ]),
15 | Expr::string("a Rust LibraryLink function"),
16 | ]);
17 |
18 | // Evaluate the message expression.
19 | let _: Expr = wll::evaluate(&message);
20 | }
21 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/docs/main.rs:
--------------------------------------------------------------------------------
1 | mod convert {
2 | mod manual_wstp;
3 | mod using_expr;
4 | }
5 |
6 | mod evaluate_wolfram_code_from_rust {
7 | mod generate_message;
8 | }
9 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/exprs/basic_expressions.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link::{
2 | self as wll,
3 | expr::{Expr, Symbol},
4 | };
5 |
6 | /// This function is loaded by evaluating:
7 | ///
8 | /// ```wolfram
9 | /// LibraryFunctionLoad[
10 | /// "/path/to/libbasic_expressions.dylib",
11 | /// "echo_arguments",
12 | /// LinkObject,
13 | /// LinkObject
14 | /// ]
15 | /// ```
16 | #[wll::export(wstp)]
17 | pub fn echo_arguments(args: Vec) -> Expr {
18 | let arg_count = args.len();
19 |
20 | for arg in args {
21 | // Echo[]
22 | wll::evaluate(&Expr::normal(Symbol::new("System`Echo"), vec![arg]));
23 | }
24 |
25 | Expr::string(format!("finished echoing {} argument(s)", arg_count))
26 | }
27 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/exprs/managed.rs:
--------------------------------------------------------------------------------
1 | use std::{collections::HashMap, sync::Mutex};
2 |
3 | use once_cell::sync::Lazy;
4 |
5 | use wolfram_library_link::{
6 | self as wll,
7 | expr::{Expr, ExprKind, Symbol},
8 | managed::{Id, ManagedExpressionEvent},
9 | };
10 |
11 | wll::generate_loader![load_managed_exprs_functions];
12 |
13 | /// Storage for all instances of [`MyObject`] associated with managed expressions
14 | /// created using `CreateManagedLibraryExpression`.
15 | static INSTANCES: Lazy>> =
16 | Lazy::new(|| Mutex::new(HashMap::new()));
17 |
18 | #[derive(Clone)]
19 | struct MyObject {
20 | value: String,
21 | }
22 |
23 | #[wll::init]
24 | fn init() {
25 | // Register `manage_instance()` as the handler for managed expressions created using:
26 | //
27 | // CreateManagedLibraryExpression["my_object", _]
28 | wll::managed::register_library_expression_manager("my_object", manage_instance);
29 | }
30 |
31 | fn manage_instance(action: ManagedExpressionEvent) {
32 | let mut instances = INSTANCES.lock().unwrap();
33 |
34 | match action {
35 | ManagedExpressionEvent::Create(id) => {
36 | // Insert a new MyObject instance with some default values.
37 | instances.insert(id, MyObject {
38 | value: String::from("default"),
39 | });
40 | },
41 | ManagedExpressionEvent::Drop(id) => {
42 | if let Some(obj) = instances.remove(&id) {
43 | drop(obj);
44 | }
45 | },
46 | }
47 | }
48 |
49 | /// Set the `MyObject.value` field for the specified instance ID.
50 | #[wll::export(wstp)]
51 | fn set_instance_value(args: Vec) {
52 | assert!(args.len() == 2, "set_instance_value: expected 2 arguments");
53 |
54 | let id: u32 = unwrap_id_arg(&args[0]);
55 | let value: String = match args[1].kind() {
56 | ExprKind::String(str) => str.clone(),
57 | _ => panic!("expected 2nd argument to be a String, got: {}", args[1]),
58 | };
59 |
60 | let mut instances = INSTANCES.lock().unwrap();
61 |
62 | let instance: &mut MyObject =
63 | instances.get_mut(&id).expect("instance does not exist");
64 |
65 | instance.value = value;
66 | }
67 |
68 | /// Get the fields of the `MyObject` instance for the specified instance ID.
69 | #[wll::export(wstp)]
70 | fn get_instance_data(args: Vec) -> Expr {
71 | assert!(args.len() == 1, "get_instance_data: expected 1 argument");
72 |
73 | let id: u32 = unwrap_id_arg(&args[0]);
74 |
75 | let MyObject { value } = {
76 | let instances = INSTANCES.lock().unwrap();
77 |
78 | instances
79 | .get(&id)
80 | .cloned()
81 | .expect("instance does not exist")
82 | };
83 |
84 | Expr::normal(Symbol::new("System`Association"), vec![Expr::normal(
85 | Symbol::new("System`Rule"),
86 | vec![Expr::string("Value"), Expr::string(value)],
87 | )])
88 | }
89 |
90 | fn unwrap_id_arg(arg: &Expr) -> u32 {
91 | match arg.kind() {
92 | ExprKind::Integer(int) => u32::try_from(*int).expect("id overflows u32"),
93 | _ => panic!("expected Integer instance ID argument, got: {}", arg),
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/numeric_arrays.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link::{self as wll, NumericArray, NumericArrayKind};
2 |
3 | /// This function is loaded by evaluating:
4 | ///
5 | /// ```wolfram
6 | /// LibraryFunctionLoad[
7 | /// "/path/to/libnumeric_arrays.dylib",
8 | /// "sum_int_numeric_array",
9 | /// {NumericArray},
10 | /// Integer
11 | /// ]
12 | /// ```
13 | #[wll::export]
14 | fn sum_int_numeric_array(na: &NumericArray) -> i64 {
15 | #[rustfmt::skip]
16 | let sum: i64 = match na.kind() {
17 | NumericArrayKind::Bit8(na) => na.as_slice().into_iter().copied().map(i64::from).sum(),
18 | NumericArrayKind::Bit16(na) => na.as_slice().into_iter().copied().map(i64::from).sum(),
19 | NumericArrayKind::Bit32(na) => na.as_slice().into_iter().copied().map(i64::from).sum(),
20 | NumericArrayKind::Bit64(na) => na.as_slice().into_iter().sum(),
21 | NumericArrayKind::UBit8(na) => na.as_slice().into_iter().copied().map(i64::from).sum(),
22 | NumericArrayKind::UBit16(na) => na.as_slice().into_iter().copied().map(i64::from).sum(),
23 | NumericArrayKind::UBit32(na) => na.as_slice().into_iter().copied().map(i64::from).sum(),
24 | NumericArrayKind::UBit64(na) => {
25 | match i64::try_from(na.as_slice().into_iter().sum::()) {
26 | Ok(sum) => sum,
27 | Err(_) => panic!("NumericArray UnsignedInteger64 sum overflows i64"),
28 | }
29 | },
30 |
31 | NumericArrayKind::Real32(_)
32 | | NumericArrayKind::Real64(_)
33 | | NumericArrayKind::ComplexReal64(_) => panic!(
34 | "sum_int_numeric_array cannot handle non-integer data type: {:?}",
35 | na.data_type()
36 | ),
37 | };
38 |
39 | sum
40 | }
41 |
42 | #[wll::export]
43 | fn sum_real_numeric_array(na: &NumericArray) -> f64 {
44 | let sum: f64 = match na.kind() {
45 | NumericArrayKind::Real32(na) => {
46 | na.as_slice().into_iter().copied().map(f64::from).sum()
47 | },
48 | NumericArrayKind::Real64(na) => na.as_slice().into_iter().copied().sum(),
49 | _ => panic!(
50 | "sum_real_numeric_array cannot handle non-real data type: {:?}",
51 | na.data_type()
52 | ),
53 | };
54 |
55 | sum
56 | }
57 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/raw/raw_librarylink_function.rs:
--------------------------------------------------------------------------------
1 | //! This example demonstrates using the raw Rust wrappers around the LibraryLink C API to
2 | //! write a function which looks much like a classic C function using LibraryLink would.
3 |
4 | use std::mem::MaybeUninit;
5 | use std::os::raw::c_uint;
6 |
7 | use wolfram_library_link::sys::{
8 | mint, MArgument, MNumericArray, MNumericArray_Data_Type, WolframLibraryData,
9 | LIBRARY_FUNCTION_ERROR, LIBRARY_NO_ERROR,
10 | };
11 |
12 | /// This function is loaded by evaluating:
13 | ///
14 | /// ```wolfram
15 | /// LibraryFunctionLoad[
16 | /// "/path/to/target/debug/examples/libraw_librarylink_function.dylib",
17 | /// "demo_function",
18 | /// {Integer, Integer},
19 | /// Integer
20 | /// ]
21 | /// ```
22 | #[no_mangle]
23 | pub unsafe extern "C" fn demo_function(
24 | _lib_data: WolframLibraryData,
25 | arg_count: mint,
26 | args: *mut MArgument,
27 | res: MArgument,
28 | ) -> c_uint {
29 | if arg_count != 2 {
30 | return LIBRARY_FUNCTION_ERROR;
31 | }
32 |
33 | let a: i64 = *(*args.offset(0)).integer;
34 | let b: i64 = *(*args.offset(1)).integer;
35 |
36 | *res.integer = a + b;
37 |
38 | LIBRARY_NO_ERROR
39 | }
40 |
41 | //======================================
42 | // NumericArray's
43 | //======================================
44 |
45 | /// This function is loaded by evaluating:
46 | ///
47 | /// ```wolfram
48 | /// LibraryFunctionLoad[
49 | /// "/path/to/target/debug/examples/libraw_librarylink_function.dylib",
50 | /// "demo_function",
51 | /// {},
52 | /// LibraryDataType[ByteArray]
53 | /// ]
54 | /// ```
55 | #[no_mangle]
56 | pub unsafe extern "C" fn demo_byte_array(
57 | lib_data: WolframLibraryData,
58 | arg_count: mint,
59 | _args: *mut MArgument,
60 | res: MArgument,
61 | ) -> c_uint {
62 | const LENGTH: usize = 10;
63 |
64 | if arg_count != 0 {
65 | return LIBRARY_FUNCTION_ERROR;
66 | }
67 |
68 | let na_funs = *(*lib_data).numericarrayLibraryFunctions;
69 |
70 | //
71 | // Allocate a new MNumericArray with 10 u8 elements
72 | //
73 |
74 | let mut byte_array: MNumericArray = std::ptr::null_mut();
75 |
76 | let err = (na_funs.MNumericArray_new.unwrap())(
77 | MNumericArray_Data_Type::MNumericArray_Type_UBit8,
78 | 1,
79 | &10,
80 | &mut byte_array,
81 | );
82 | if err != 0 {
83 | return LIBRARY_FUNCTION_ERROR;
84 | }
85 |
86 | //
87 | // Fill the NumericArray with the number 1 to 10
88 | //
89 |
90 | let data_ptr: *mut std::ffi::c_void =
91 | (na_funs.MNumericArray_getData.unwrap())(byte_array);
92 | let data_ptr = data_ptr as *mut MaybeUninit;
93 |
94 | let slice = std::slice::from_raw_parts_mut(data_ptr, LENGTH);
95 |
96 | for (index, elem) in slice.iter_mut().enumerate() {
97 | *elem = MaybeUninit::new(index as u8)
98 | }
99 |
100 | //
101 | // Return the NumericArray
102 | //
103 |
104 | *res.numeric = byte_array;
105 | LIBRARY_NO_ERROR
106 | }
107 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/raw/raw_wstp_function.rs:
--------------------------------------------------------------------------------
1 | //! This example demonstrates using the raw Rust wrappers around the LibraryLink C API to
2 | //! write a function which looks much like a classic C function using LibraryLink and
3 | //! WSTP would.
4 | //!
5 | //! This also includes an example of mixing the low-level LibraryLink bindings with the
6 | //! higher-level bindings provided by the `wstp` crate.
7 |
8 | use std::os::raw::{c_int, c_uint};
9 |
10 | use wolfram_library_link::{
11 | expr::{Expr, Symbol},
12 | sys::{
13 | self as wll_sys, WolframLibraryData, LIBRARY_FUNCTION_ERROR, LIBRARY_NO_ERROR,
14 | },
15 | wstp::{
16 | sys::{WSGetInteger, WSNewPacket, WSPutInteger, WSTestHead, WSLINK},
17 | Link,
18 | },
19 | };
20 |
21 | /// This function is loaded by evaluating:
22 | ///
23 | /// ```wolfram
24 | /// LibraryFunctionLoad[
25 | /// "/path/to/libraw_wstp_function.dylib",
26 | /// "demo_wstp_function",
27 | /// LinkObject,
28 | /// LinkObject
29 | /// ]
30 | /// ```
31 | #[no_mangle]
32 | pub unsafe extern "C" fn demo_wstp_function(
33 | _lib: WolframLibraryData,
34 | link: WSLINK,
35 | ) -> c_uint {
36 | let mut i1: c_int = 0;
37 | let mut i2: c_int = 0;
38 | let mut len: c_int = 0;
39 |
40 | if WSTestHead(link, b"List\0".as_ptr() as *const i8, &mut len) == 0 {
41 | return LIBRARY_FUNCTION_ERROR;
42 | }
43 | if len != 2 {
44 | return LIBRARY_FUNCTION_ERROR;
45 | }
46 |
47 | if WSGetInteger(link, &mut i1) == 0 {
48 | return LIBRARY_FUNCTION_ERROR;
49 | }
50 | if WSGetInteger(link, &mut i2) == 0 {
51 | return LIBRARY_FUNCTION_ERROR;
52 | }
53 | if WSNewPacket(link) == 0 {
54 | return LIBRARY_FUNCTION_ERROR;
55 | }
56 |
57 | let sum = i1 + i2;
58 |
59 | if WSPutInteger(link, sum) == 0 {
60 | return LIBRARY_FUNCTION_ERROR;
61 | }
62 |
63 | return LIBRARY_NO_ERROR;
64 | }
65 |
66 | /// This example shows how the raw Rust wrappers can be mixed with higher-level wrappers
67 | /// around the Wolfram Symbolic Transfer Protocal (WSTP) for conveniently calling back
68 | /// into the Kernel to perform an evaluation.
69 | ///
70 | /// This function is loaded by evaluating:
71 | ///
72 | /// ```wolfram
73 | /// LibraryFunctionLoad[
74 | /// "/path/to/libraw_wstp_function.dylib",
75 | /// "demo_wstp_function_callback",
76 | /// LinkObject,
77 | /// LinkObject
78 | /// ]
79 | /// ```
80 | #[no_mangle]
81 | pub extern "C" fn demo_wstp_function_callback(
82 | lib: WolframLibraryData,
83 | mut link: WSLINK,
84 | ) -> c_uint {
85 | // Create a safe Link wrapper around the raw `WSLINK`. This is a borrowed rather than
86 | // owned Link because the caller (the Kernel) owns the link.
87 | let link: &mut Link = unsafe { Link::unchecked_ref_cast_mut(&mut link) };
88 |
89 | // Skip reading the argument list packet.
90 | if link.raw_get_next().and_then(|_| link.new_packet()).is_err() {
91 | return LIBRARY_FUNCTION_ERROR;
92 | }
93 |
94 | let callback_link = unsafe { (*lib).getWSLINK.unwrap()(lib) };
95 | let mut callback_link = callback_link as wstp::sys::WSLINK;
96 |
97 | {
98 | let safe_callback_link =
99 | unsafe { Link::unchecked_ref_cast_mut(&mut callback_link) };
100 |
101 | safe_callback_link
102 | // EvaluatePacket[Print["Hello, World! --- WSTP"]]
103 | .put_expr(&Expr::normal(Symbol::new("System`EvaluatePacket"), vec![
104 | Expr::normal(Symbol::new("System`Print"), vec![Expr::string(
105 | "Hello, World! --- WSTP",
106 | )]),
107 | ]))
108 | .unwrap();
109 |
110 | unsafe {
111 | (*lib).processWSLINK.unwrap()(
112 | safe_callback_link.raw_link() as wll_sys::WSLINK
113 | );
114 | }
115 |
116 | // Skip the return value packet. This is necessary, otherwise the link has
117 | // unread data and the return value of this function cannot be processed properly.
118 | if safe_callback_link
119 | .raw_get_next()
120 | .and_then(|_| safe_callback_link.new_packet())
121 | .is_err()
122 | {
123 | return LIBRARY_FUNCTION_ERROR;
124 | }
125 | }
126 |
127 | link.put_expr(&Expr::string("returned normally")).unwrap();
128 |
129 | return LIBRARY_NO_ERROR;
130 | }
131 |
132 | /// This example makes use of the [`wstp`][wstp] crate to provide a safe wrapper around
133 | /// around the WSTP link object, which can be used to read the argument expression and
134 | /// write out the return expression.
135 | ///
136 | /// ```wolfram
137 | /// function = LibraryFunctionLoad[
138 | /// "raw_wstp_function",
139 | /// "wstp_expr_function",
140 | /// LinkObject,
141 | /// LinkObject
142 | /// ];
143 | /// ```
144 | #[no_mangle]
145 | pub extern "C" fn wstp_expr_function(
146 | _lib: WolframLibraryData,
147 | mut unsafe_link: WSLINK,
148 | ) -> c_uint {
149 | let link: &mut Link = unsafe { Link::unchecked_ref_cast_mut(&mut unsafe_link) };
150 |
151 | let expr = match link.get_expr() {
152 | Ok(expr) => expr,
153 | Err(err) => {
154 | // Skip reading the argument list packet.
155 | if link.raw_get_next().and_then(|_| link.new_packet()).is_err() {
156 | return LIBRARY_FUNCTION_ERROR;
157 | }
158 |
159 | let err = Expr::string(err.to_string());
160 | let err = Expr::normal(Symbol::new("System`Failure"), vec![
161 | Expr::string("WSTP Error"),
162 | Expr::normal(Symbol::new("System`Association"), vec![Expr::normal(
163 | Symbol::new("System`Rule"),
164 | vec![Expr::string("Message"), err],
165 | )]),
166 | ]);
167 | match link.put_expr(&err) {
168 | Ok(()) => return LIBRARY_NO_ERROR,
169 | Err(_) => return LIBRARY_FUNCTION_ERROR,
170 | }
171 | },
172 | };
173 |
174 | let expr_string = format!("Input: {}", expr.to_string());
175 |
176 | match link.put_expr(&Expr::string(expr_string)) {
177 | Ok(()) => LIBRARY_NO_ERROR,
178 | Err(_) => LIBRARY_FUNCTION_ERROR,
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/tests/README.md:
--------------------------------------------------------------------------------
1 | # wolfram-library-link/examples/tests/
2 |
3 | This directory contains tests for wolfram-library-link functionality, which are declared
4 | as cargo examples so that they produce dynamic libraries that can be loaded by
5 | standard Wolfram Language MUnit tests.
6 |
7 | This is necessary because much of wolfram-library-link depends on being dynamically linked
8 | into a running Wolfram Language Kernel, so tests for that functionality must necessarily
9 | be initiated by the Kernel. Writing these as cargo integration tests run using the
10 | standard `cargo test` command would fail because there would be no Wolfram Kernel to load
11 | and call the LibraryLink functions being tested.
12 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/tests/main.rs:
--------------------------------------------------------------------------------
1 | mod test_native_args;
2 | mod test_share_counts;
3 | mod test_threading;
4 |
5 | mod test_data_store;
6 | mod test_images;
7 | mod test_numeric_array_conversions;
8 | mod test_wstp;
9 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/tests/test_data_store.rs:
--------------------------------------------------------------------------------
1 | use std::os::raw::c_int;
2 | use wolfram_library_link::{
3 | self as wll,
4 | sys::{self, WolframLibraryData},
5 | DataStore, NumericArray,
6 | };
7 |
8 |
9 | #[no_mangle]
10 | pub unsafe extern "C" fn WolframLibrary_initialize(lib: WolframLibraryData) -> c_int {
11 | match wll::initialize(lib) {
12 | Ok(()) => return 0,
13 | Err(()) => return 1,
14 | }
15 | }
16 |
17 | #[wll::export]
18 | fn test_empty_data_store() -> DataStore {
19 | DataStore::new()
20 | }
21 |
22 | #[wll::export]
23 | fn test_single_int_data_store() -> DataStore {
24 | let mut data = DataStore::new();
25 | data.add_i64(1);
26 |
27 | data
28 | }
29 |
30 | #[wll::export]
31 | fn test_multiple_int_data_store() -> DataStore {
32 | let mut data = DataStore::new();
33 | data.add_i64(1);
34 | data.add_i64(2);
35 | data.add_i64(3);
36 |
37 | data
38 | }
39 |
40 | #[wll::export]
41 | fn test_unnamed_heterogenous_data_store() -> DataStore {
42 | let mut data = DataStore::new();
43 | data.add_i64(1);
44 | data.add_f64(2.0);
45 | data.add_str("hello");
46 |
47 | data
48 | }
49 |
50 | #[wll::export]
51 | fn test_named_heterogenous_data_store() -> DataStore {
52 | let mut data = DataStore::new();
53 | data.add_named_i64("an i64", 1);
54 | data.add_named_f64("an f64", 2.0);
55 | data.add_named_str("a str", "hello");
56 |
57 | data
58 | }
59 |
60 | #[wll::export]
61 | fn test_named_and_unnamed_heterogenous_data_store() -> DataStore {
62 | let mut data = DataStore::new();
63 | data.add_i64(1);
64 | data.add_named_f64("real", 2.0);
65 | data.add_named_str("hello", "world");
66 |
67 | data
68 | }
69 |
70 | //======================================
71 | // Non-atomic types
72 | //======================================
73 |
74 | #[wll::export]
75 | fn test_named_numeric_array_data_store() -> DataStore {
76 | let array = NumericArray::::from_slice(&[1, 2, 3]).into_generic();
77 |
78 | let mut data = DataStore::new();
79 | data.add_named_numeric_array("array", array);
80 |
81 | data
82 | }
83 |
84 | #[wll::export]
85 | fn test_nested_data_store() -> DataStore {
86 | let mut inner = DataStore::new();
87 | inner.add_named_bool("is_inner", true);
88 |
89 | let mut outer = DataStore::new();
90 | outer.add_named_bool("is_inner", false);
91 | outer.add_data_store(inner);
92 |
93 | outer
94 | }
95 |
96 | #[wll::export]
97 | fn test_iterated_nested_data_store() -> DataStore {
98 | let mut store = DataStore::new();
99 |
100 | for level in 0..3 {
101 | store.add_named_i64("level", level);
102 | let mut new = DataStore::new();
103 | new.add_data_store(store);
104 | store = new;
105 | }
106 |
107 | store
108 | }
109 |
110 | //======================================
111 | // DataStore arguments
112 | //======================================
113 |
114 | #[wll::export]
115 | fn test_data_store_arg(ds: DataStore) -> i64 {
116 | ds.len() as i64
117 | }
118 |
119 | //======================================
120 | // DataStore nodes
121 | //======================================
122 |
123 | #[wll::export]
124 | fn test_data_store_nodes() {
125 | {
126 | let mut data = DataStore::new();
127 | data.add_i64(5);
128 |
129 | assert_eq!(data.len(), 1);
130 |
131 | let node = data.first_node().expect("got first node");
132 |
133 | let ty: sys::type_t = node.data_type_raw();
134 |
135 | assert_eq!(ty, sys::MType_Integer as i32);
136 | }
137 |
138 | // Test DataStoreNode::name() method.
139 | {
140 | let mut data = DataStore::new();
141 | data.add_named_i64("hello", 5);
142 |
143 | assert_eq!(data.len(), 1);
144 |
145 | let node = data.first_node().expect("got first node");
146 |
147 | assert_eq!(node.data_type_raw(), sys::MType_Integer as i32);
148 | assert_eq!(node.name(), Some("hello".to_owned()))
149 | }
150 |
151 | // Test DataStore::nodes() method and Debug formatting of DataStoreNode.
152 | {
153 | let mut store = DataStore::new();
154 |
155 | store.add_i64(5);
156 | store.add_named_bool("condition", true);
157 | store.add_str("Hello, World!");
158 |
159 | let mut nodes = store.nodes();
160 |
161 | assert_eq!(
162 | format!("{:?}", nodes.next().unwrap()),
163 | r#"DataStoreNode { name: None, value: 5 }"#
164 | );
165 | assert_eq!(
166 | format!("{:?}", nodes.next().unwrap()),
167 | r#"DataStoreNode { name: Some("condition"), value: true }"#
168 | );
169 | assert_eq!(
170 | format!("{:?}", nodes.next().unwrap()),
171 | r#"DataStoreNode { name: None, value: "Hello, World!" }"#
172 | );
173 | assert!(nodes.next().is_none());
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/tests/test_images.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link::{
2 | self as wll, ColorSpace, Image, NumericArray, Pixel, UninitImage, UninitNumericArray,
3 | };
4 |
5 |
6 | #[wll::export]
7 | fn test_image_arg(image: &Image) -> NumericArray {
8 | let mut array = UninitNumericArray::from_dimensions(&[image.flattened_length()]);
9 |
10 | for pair in image
11 | .as_slice()
12 | .iter()
13 | .zip(array.as_slice_mut().into_iter())
14 | {
15 | let (pixel, elem): (&i8, &mut std::mem::MaybeUninit) = pair;
16 | elem.write(*pixel);
17 | }
18 |
19 | // Safety: We iterated over every element in `array` and initialized it with the
20 | // corresponding pixel value from `image`.
21 | unsafe { array.assume_init() }
22 | }
23 |
24 | // fn test_image_manual_arg(image: Image) -> NumericArray {
25 | // todo!("")
26 | // }
27 |
28 | #[wll::export]
29 | fn test_create_bitmap_image() -> Image {
30 | let width = 2;
31 | let height = 2;
32 | let channels = 1;
33 |
34 | let mut image = UninitImage::::new_2d(
35 | width,
36 | height,
37 | channels,
38 | ColorSpace::Automatic,
39 | false,
40 | );
41 |
42 | image.set(Pixel::D2([1, 1]), 1, false);
43 | image.set(Pixel::D2([1, 2]), 1, true);
44 | image.set(Pixel::D2([2, 1]), 1, true);
45 | image.set(Pixel::D2([2, 2]), 1, false);
46 |
47 | unsafe { image.assume_init() }
48 | }
49 |
50 | /// Create an image with four pixels, where the top left image is red, the top right
51 | /// pixel is green, the bottom left pixel is blue, and the bottom right pixel is light
52 | /// gray.
53 | #[wll::export]
54 | fn test_create_color_rgb_u8_image() -> Image {
55 | let width = 2;
56 | let height = 2;
57 | let channels = 3; // Red, green, and blue.
58 |
59 | let mut image: UninitImage =
60 | UninitImage::new_2d(width, height, channels, ColorSpace::RGB, false);
61 |
62 | // Red, green, and blue channels indices.
63 | const R: usize = 1;
64 | const G: usize = 2;
65 | const B: usize = 3;
66 |
67 | // Set every pixel value to black. The image data is otherwise completely
68 | // uninitialized memory, and can contain arbitrary values.
69 | image.zero();
70 |
71 | // Set the top left, top right, and bottom left pixels on only one color channel.
72 | image.set(Pixel::D2([1, 1]), R, u8::MAX);
73 | image.set(Pixel::D2([1, 2]), G, u8::MAX);
74 | image.set(Pixel::D2([2, 1]), B, u8::MAX);
75 |
76 | // Make this pixel white, by setting R, G, and B channels to ~80%.
77 | image.set(Pixel::D2([2, 2]), R, 200);
78 | image.set(Pixel::D2([2, 2]), G, 200);
79 | image.set(Pixel::D2([2, 2]), B, 200);
80 |
81 | unsafe { image.assume_init() }
82 | }
83 |
84 | /// Create an image with four pixels, where the top left image is red, the top right
85 | /// pixel is green, the bottom left pixel is blue, and the bottom right pixel is light
86 | /// gray.
87 | #[wll::export]
88 | fn test_create_color_rgb_f32_image() -> Image {
89 | let width = 2;
90 | let height = 2;
91 | let channels = 3; // Red, green, and blue.
92 |
93 | let mut image: UninitImage =
94 | UninitImage::new_2d(width, height, channels, ColorSpace::RGB, false);
95 |
96 | // Red, green, and blue channels indices.
97 | const R: usize = 1;
98 | const G: usize = 2;
99 | const B: usize = 3;
100 |
101 | // Set every pixel value to black. The image data is otherwise completely
102 | // uninitialized memory, and can contain arbitrary values.
103 | image.zero();
104 |
105 | // Set the top left, top right, and bottom left pixels on only one color channel.
106 | image.set(Pixel::D2([1, 1]), R, 1.0);
107 | image.set(Pixel::D2([1, 2]), G, 1.0);
108 | image.set(Pixel::D2([2, 1]), B, 1.0);
109 |
110 | // Make this pixel white, by setting R, G, and B channels to 80%.
111 | image.set(Pixel::D2([2, 2]), R, 0.8);
112 | image.set(Pixel::D2([2, 2]), G, 0.8);
113 | image.set(Pixel::D2([2, 2]), B, 0.8);
114 |
115 | unsafe { image.assume_init() }
116 | }
117 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/tests/test_native_args.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::CString;
2 |
3 | use wolfram_library_link::{
4 | self as wll,
5 | sys::{mint, mreal},
6 | NumericArray, UninitNumericArray,
7 | };
8 |
9 | //======================================
10 | // Primitive data types
11 | //======================================
12 |
13 | #[wll::export]
14 | fn test_no_args() -> i64 {
15 | 4
16 | }
17 |
18 | #[wll::export]
19 | fn test_ret_void() {
20 | // Do nothing.
21 | }
22 |
23 | //------------
24 | // mint, mreal
25 | //------------
26 |
27 | #[wll::export]
28 | fn test_mint(x: mint) -> mint {
29 | x * x
30 | }
31 |
32 | // Test NativeFunction impl for raw function using raw MArguments.
33 | #[wll::export]
34 | fn test_raw_mint(args: &[wll::sys::MArgument], ret: wll::sys::MArgument) {
35 | if args.len() != 1 {
36 | panic!("unexpected number of arguments");
37 | }
38 |
39 | let x: mint = unsafe { *args[0].integer };
40 |
41 | unsafe {
42 | *ret.integer = x * x;
43 | }
44 | }
45 |
46 | #[wll::export]
47 | fn test_mint_mint(x: mint, y: mint) -> mint {
48 | x + y
49 | }
50 |
51 | #[wll::export]
52 | fn test_mreal(x: mreal) -> mreal {
53 | x * x
54 | }
55 |
56 | //------------
57 | // i64, f64
58 | //------------
59 |
60 | #[wll::export]
61 | fn test_i64(x: i64) -> i64 {
62 | x * x
63 | }
64 |
65 | #[wll::export]
66 | fn test_i64_i64(x: i64, y: i64) -> i64 {
67 | x + y
68 | }
69 |
70 | #[wll::export]
71 | fn test_f64(x: f64) -> f64 {
72 | x * x
73 | }
74 |
75 | //--------
76 | // Strings
77 | //--------
78 |
79 | // fn test_str(string: &str) -> String {
80 | // string.chars().rev().collect()
81 | // }
82 |
83 | #[wll::export]
84 | fn test_string(string: String) -> String {
85 | string.chars().rev().collect()
86 | }
87 |
88 | #[wll::export]
89 | fn test_c_string(string: CString) -> i64 {
90 | i64::try_from(string.as_bytes().len()).expect("string len usize overflows i64")
91 | }
92 |
93 | //-------
94 | // Panics
95 | //-------
96 |
97 | #[wll::export]
98 | fn test_panic() {
99 | panic!("this function panicked");
100 | }
101 |
102 | //======================================
103 | // NumericArray's
104 | //======================================
105 |
106 | #[wll::export]
107 | fn total_i64(list: &NumericArray) -> i64 {
108 | list.as_slice().into_iter().sum()
109 | }
110 |
111 | /// Get the sign of every element in `list` as a numeric array of 0's and 1's.
112 | ///
113 | /// The returned array will have the same dimensions as `list`.
114 | #[wll::export]
115 | fn positive_i64(list: &NumericArray) -> NumericArray {
116 | let mut bools: UninitNumericArray =
117 | UninitNumericArray::from_dimensions(list.dimensions());
118 |
119 | for pair in list.as_slice().into_iter().zip(bools.as_slice_mut()) {
120 | let (elem, entry): (&i64, &mut std::mem::MaybeUninit) = pair;
121 |
122 | entry.write(u8::from(elem.is_positive()));
123 | }
124 |
125 | unsafe { bools.assume_init() }
126 | }
127 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/tests/test_numeric_array_conversions.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link::{
2 | self as wll, NumericArray, NumericArrayConvertMethod as Method,
3 | };
4 |
5 | fn from_slice(slice: &[T]) -> NumericArray {
6 | NumericArray::from_slice(slice)
7 | }
8 |
9 | #[wll::export]
10 | fn test_na_conversions() {
11 | //
12 | // i16 -> i8 conversions
13 | //
14 |
15 | assert!(from_slice(&[i16::MAX])
16 | .convert_to::(Method::Check, 1.0)
17 | .is_err());
18 |
19 | assert!(from_slice(&[i16::MAX])
20 | .convert_to::(Method::Cast, 1.0)
21 | .is_err());
22 |
23 | assert!(from_slice(&[i16::MAX])
24 | .convert_to::(Method::Coerce, 1.0)
25 | .is_err());
26 |
27 | assert!(from_slice(&[i16::MAX])
28 | .convert_to::(Method::Round, 1.0)
29 | .is_err());
30 |
31 | assert_eq!(
32 | from_slice(&[i16::MAX])
33 | .convert_to::(Method::Scale, 1.0)
34 | .unwrap()
35 | .as_slice(),
36 | [i8::MAX]
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/tests/test_share_counts.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link::{self as wll, DataStore, NumericArray};
2 |
3 |
4 | #[wll::export]
5 | fn test_na_automatic_count(array: &NumericArray) -> i64 {
6 | array.share_count() as i64
7 | }
8 |
9 | #[wll::export]
10 | fn test_na_constant_count(array: &NumericArray) -> i64 {
11 | array.share_count() as i64
12 | }
13 |
14 | #[wll::export]
15 | fn test_na_manual_count(array: NumericArray) -> i64 {
16 | array.share_count() as i64
17 | }
18 |
19 | #[wll::export]
20 | fn test_na_shared_count(array: NumericArray) -> i64 {
21 | array.share_count() as i64
22 | }
23 |
24 | //
25 |
26 | #[wll::export]
27 | fn test_na_constant_are_ptr_eq(
28 | array1: &NumericArray,
29 | array2: &NumericArray,
30 | ) -> DataStore {
31 | let mut data = DataStore::new();
32 | data.add_bool(array1.ptr_eq(&array2));
33 | data.add_i64(array1.share_count() as i64);
34 | data
35 | }
36 |
37 | #[wll::export]
38 | fn test_na_manual_are_not_ptr_eq(
39 | mut array1: NumericArray,
40 | array2: NumericArray,
41 | ) -> DataStore {
42 | let mut data = DataStore::new();
43 | data.add_bool(array1.ptr_eq(&array2));
44 | data.add_i64(array1.share_count() as i64);
45 | data.add_bool(array1.as_slice_mut().is_some());
46 | data
47 | }
48 |
49 | #[wll::export]
50 | fn test_na_shared_are_ptr_eq(
51 | mut array1: NumericArray,
52 | array2: NumericArray,
53 | ) -> DataStore {
54 | let mut data = DataStore::new();
55 | data.add_bool(array1.ptr_eq(&array2));
56 | data.add_i64(array1.share_count() as i64);
57 | data.add_bool(array1.as_slice_mut().is_some());
58 | data
59 | }
60 |
61 | //----------------------------
62 | // Test cloning NumericArray's
63 | //----------------------------
64 |
65 | #[wll::export]
66 | fn test_na_clone() -> bool {
67 | let array = NumericArray::::from_slice(&[1, 2, 3]);
68 |
69 | assert!(array.share_count() == 0);
70 |
71 | let clone = array.clone();
72 |
73 | assert!(!array.ptr_eq(&clone));
74 |
75 | assert!(array.share_count() == 0);
76 | assert!(clone.share_count() == 0);
77 |
78 | true
79 | }
80 |
81 | #[wll::export]
82 | fn test_na_shared_clone(array: NumericArray) -> bool {
83 | assert!(array.share_count() == 1);
84 |
85 | let clone = array.clone();
86 |
87 | assert!(!array.ptr_eq(&clone));
88 |
89 | assert!(array.share_count() == 1);
90 | assert!(clone.share_count() == 0);
91 |
92 | true
93 | }
94 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/tests/test_threading.rs:
--------------------------------------------------------------------------------
1 | use std::panic;
2 |
3 | use wolfram_library_link::{
4 | self as wll,
5 | expr::{Expr, Symbol},
6 | };
7 |
8 | #[wll::export]
9 | fn test_runtime_function_from_main_thread() -> bool {
10 | let expr = Expr::normal(Symbol::new("System`Plus"), vec![
11 | Expr::from(2),
12 | Expr::from(2),
13 | ]);
14 |
15 | wll::evaluate(&expr) == Expr::from(4)
16 | }
17 |
18 | #[wll::export]
19 | fn test_runtime_function_from_non_main_thread() -> String {
20 | let child = std::thread::spawn(|| {
21 | panic::set_hook(Box::new(|_| {
22 | // Do nothing, just to avoid printing panic message to stderr.
23 | }));
24 |
25 | let result = panic::catch_unwind(|| {
26 | wll::evaluate(&Expr::normal(Symbol::new("System`Plus"), vec![
27 | Expr::from(2),
28 | Expr::from(2),
29 | ]))
30 | });
31 |
32 | // Restore the previous (default) hook.
33 | let _ = panic::take_hook();
34 |
35 | result
36 | });
37 |
38 | let result = child.join().unwrap();
39 |
40 | match result {
41 | Ok(_) => "didn't panic".to_owned(),
42 | // We expect the thread to panic
43 | Err(panic) => {
44 | if let Some(str) = panic.downcast_ref::<&str>() {
45 | format!("PANIC: {}", str)
46 | } else if let Some(string) = panic.downcast_ref::() {
47 | format!("PANIC: {}", string)
48 | } else {
49 | "PANIC".to_owned()
50 | }
51 | },
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/tests/test_wstp.rs:
--------------------------------------------------------------------------------
1 | use wolfram_library_link::{
2 | export,
3 | expr::Expr,
4 | wstp::{self, Link},
5 | };
6 |
7 | #[export(wstp)]
8 | fn test_wstp_fn_empty(_link: &mut Link) {
9 | // Do nothing.
10 | }
11 |
12 | #[export(wstp)]
13 | fn test_wstp_fn_panic_immediately(_link: &mut Link) {
14 | panic!("successful panic")
15 | }
16 |
17 | #[export(wstp)]
18 | fn test_wstp_fn_panic_immediately_with_formatting(_link: &mut Link) {
19 | panic!("successful {} panic", "formatted")
20 | }
21 |
22 | /// Test that the wrapper function generated by `#[export(wstp)]` will correctly handle
23 | /// panicking when `link` has been left in a `!link.is_ready()` state.
24 | #[export(wstp)]
25 | fn test_wstp_panic_with_empty_link(link: &mut Link) {
26 | link.raw_get_next().unwrap();
27 | link.new_packet().unwrap();
28 |
29 | assert!(!link.is_ready());
30 | assert!(link.error().is_none());
31 |
32 | // Panic while there is no content on `link` to be cleared by the panic handler.
33 | // This essentially tests that the panic handler checks `if link.is_ready() { ... }`
34 | // before trying to clear content off of the link.
35 | panic!("panic while !link.is_ready()");
36 | }
37 |
38 | #[export(wstp)]
39 | fn test_wstp_fn_panic_partial_result(link: &mut Link) {
40 | link.raw_get_next().unwrap();
41 | link.new_packet().unwrap();
42 |
43 | assert!(!link.is_ready());
44 | assert!(link.error().is_none());
45 |
46 |
47 | // Indicate that we will be writing a 3 element list, but only write one
48 | // of the elements.
49 | link.put_function("System`List", 3).unwrap();
50 | link.put_i64(1).unwrap();
51 |
52 | // Now panic with the link containing partial data.
53 | panic!("incomplete result")
54 | }
55 |
56 | #[export(wstp)]
57 | fn test_wstp_fn_return_partial_result(link: &mut Link) {
58 | link.raw_get_next().unwrap();
59 | link.new_packet().unwrap();
60 |
61 | assert!(!link.is_ready());
62 | assert!(link.error().is_none());
63 |
64 | link.put_function("System`List", 3).unwrap();
65 |
66 | // Return without completing the List expression
67 | }
68 |
69 | /// Test that the wrapper function generated by `#[export(wstp)]` will check for and clear
70 | /// any link errors that might have occurred within the user code.
71 | #[export(wstp)]
72 | fn test_wstp_fn_poison_link_and_panic(link: &mut Link) {
73 | // Cause a link failure by trying to Get the wrong head.
74 | assert!(link.test_head("NotTheRightHead").is_err());
75 |
76 | // Assert that the link now has an uncleared error.
77 | assert_eq!(link.error().unwrap().code(), Some(wstp::sys::WSEGSEQ));
78 |
79 | // Verify that trying to do an operation on the link returns the same error as
80 | // `link.error()`.
81 | assert_eq!(
82 | link.put_str("some result").unwrap_err().code(),
83 | Some(wstp::sys::WSEGSEQ)
84 | );
85 |
86 | // Panic while leaving the link in the error state.
87 | panic!("successful panic")
88 | }
89 |
90 | #[export(wstp)]
91 | fn test_wstp_expr_return_null(_args: Vec) {
92 | // Do nothing.
93 | }
94 |
--------------------------------------------------------------------------------
/wolfram-library-link/examples/wstp.rs:
--------------------------------------------------------------------------------
1 | //! This example demonstrates how WSTP links can be used in LibraryLink functions to pass
2 | //! arbitrary expressions as the function arguments and return value.
3 |
4 | use wolfram_library_link::{
5 | self as wll,
6 | expr::{Expr, ExprKind, Number, Symbol},
7 | wstp::Link,
8 | };
9 |
10 | // Generates a special "loader" function, which returns an Association containing the
11 | // loaded forms of all functions exported by this library.
12 | //
13 | // The loader can be loaded and used by evaluating:
14 | //
15 | // ```
16 | // loadFunctions = LibraryFunctionLoad[
17 | // "libwstp_example",
18 | // "load_wstp_functions",
19 | // LinkObject,
20 | // LinkObject
21 | // ];
22 | //
23 | // $functions = loadFunctions["libwstp_example"];
24 | // ```
25 | wll::generate_loader!(load_wstp_functions);
26 |
27 | //======================================
28 | // Using `&mut Link`
29 | //======================================
30 |
31 | //------------------
32 | // square_wstp()
33 | //------------------
34 |
35 | /// Define a WSTP function that squares a number.
36 | ///
37 | /// ```wolfram
38 | /// square = $functions["square_wstp"];
39 | ///
40 | /// square[4] (* Returns 16 *)
41 | /// ```
42 | #[wll::export(wstp)]
43 | fn square_wstp(link: &mut Link) {
44 | // Get the number of elements in the arguments list.
45 | let arg_count: usize = link.test_head("System`List").unwrap();
46 |
47 | if arg_count != 1 {
48 | panic!("square_wstp: expected to get a single argument");
49 | }
50 |
51 | // Get the argument value.
52 | let x = link.get_i64().expect("expected Integer argument");
53 |
54 | // Write the return value.
55 | link.put_i64(x * x).unwrap();
56 | }
57 |
58 | //------------------
59 | // count_args()
60 | //------------------
61 |
62 | /// Define a function that returns an integer count of the number of arguments it was
63 | /// given.
64 | ///
65 | /// The exported LibraryLink function can be loaded and used by evaluating:
66 | ///
67 | /// ```wolfram
68 | /// countArgs = $functions["count_args"];
69 | ///
70 | /// countArgs[a] (* Returns 1)
71 | /// countArgs[a, b, c] (* Returns 3 *)
72 | /// ```
73 | #[wll::export(wstp)]
74 | fn count_args(link: &mut Link) {
75 | // Get the number of elements in the arguments list.
76 | let arg_count: usize = link.test_head("System`List").unwrap();
77 |
78 | // Discard the remaining argument data.
79 | link.new_packet().unwrap();
80 |
81 | // Write the return value.
82 | link.put_i64(i64::try_from(arg_count).unwrap()).unwrap();
83 | }
84 |
85 | //------------------
86 | // total_args_i64()
87 | //------------------
88 |
89 | /// Define a function that returns the sum of it's integer arguments.
90 | ///
91 | /// The exported LibraryLink function can be loaded and used by evaluating:
92 | ///
93 | /// ```wolfram
94 | /// totalArgsI64 = $functions["total_args_i64"];
95 | ///
96 | /// totalArgsI64[1, 1, 2, 3, 5] (* Returns 12 *)
97 | /// ```
98 | #[wll::export(wstp)]
99 | fn total_args_i64(link: &mut Link) {
100 | // Check that we recieved a functions arguments list, and get the number of arguments.
101 | let arg_count: usize = link.test_head("System`List").unwrap();
102 |
103 | let mut total: i64 = 0;
104 |
105 | // Get each argument, assuming that they are all integers, and add it to the total.
106 | for _ in 0..arg_count {
107 | let term = link.get_i64().expect("expected Integer argument");
108 | total += term;
109 | }
110 |
111 | // Write the return value to the link.
112 | link.put_i64(total).unwrap();
113 | }
114 |
115 | //------------------
116 | // string_join()
117 | //------------------
118 |
119 | /// Define a function that will join its string arguments into a single string.
120 | ///
121 | /// The exported LibraryLink function can be loaded and used by evaluating:
122 | ///
123 | /// ```wolfram
124 | /// stringJoin = $functions["string_join"];
125 | ///
126 | /// stringJoin["Foo", "Bar"] (* Returns "FooBar" *)
127 | /// stringJoin["Foo", "Bar", "Baz"] (* Returns "FooBarBaz" *)
128 | /// stringJoin[] (* Returns "" *)
129 | /// ```
130 | #[wll::export(wstp)]
131 | fn string_join(link: &mut Link) {
132 | use wstp::LinkStr;
133 |
134 | let arg_count = link.test_head("System`List").unwrap();
135 |
136 | let mut buffer = String::new();
137 |
138 | for _ in 0..arg_count {
139 | let elem: LinkStr<'_> = link.get_string_ref().expect("expected String argument");
140 | buffer.push_str(elem.as_str());
141 | }
142 |
143 | // Write the joined string value to the link.
144 | link.put_str(buffer.as_str()).unwrap();
145 | }
146 |
147 | //------------------
148 | // link_expr_identity()
149 | //------------------
150 |
151 | /// Define a function that returns the argument expression that was sent over the link.
152 | /// That expression will be a list of the arguments passed to this LibraryFunction[..].
153 | ///
154 | /// ```wolfram
155 | /// linkExprIdentity = $functions["link_expr_identity"];
156 | ///
157 | /// linkExprIdentity[5] (* Returns {5} *)
158 | /// linkExprIdentity[a, b] (* Returns {a, b} *)
159 | /// ```
160 | #[wll::export(wstp)]
161 | fn link_expr_identity(link: &mut Link) {
162 | let expr = link.get_expr().unwrap();
163 | assert!(!link.is_ready());
164 | link.put_expr(&expr).unwrap();
165 | }
166 |
167 | //------------------
168 | // expr_string_join()
169 | //------------------
170 |
171 | /// This example is an alternative to the `string_join()` example.
172 | ///
173 | /// This example shows using the `Expr` and `ExprKind` types to process expressions on
174 | /// the WSTP link.
175 | #[wll::export(wstp)]
176 | fn expr_string_join(link: &mut Link) {
177 | let expr = link.get_expr().unwrap();
178 |
179 | let list = expr.try_as_normal().unwrap();
180 | assert!(list.has_head(&Symbol::new("System`List")));
181 |
182 | let mut buffer = String::new();
183 | for elem in list.elements() {
184 | match elem.kind() {
185 | ExprKind::String(str) => buffer.push_str(str),
186 | _ => panic!("expected String argument, got: {:?}", elem),
187 | }
188 | }
189 |
190 | link.put_str(buffer.as_str()).unwrap()
191 | }
192 |
193 | //======================================
194 | // Using `Vec` argument list
195 | //======================================
196 |
197 | //------------------
198 | // total()
199 | //------------------
200 |
201 | #[wll::export(wstp)]
202 | fn total(args: Vec) -> Expr {
203 | let mut total = Number::Integer(0);
204 |
205 | for (index, arg) in args.into_iter().enumerate() {
206 | let number = match arg.try_as_number() {
207 | Some(number) => number,
208 | None => panic!(
209 | "expected argument at position {} to be a number, got {}",
210 | // Add +1 to display using WL 1-based indexing.
211 | index + 1,
212 | arg
213 | ),
214 | };
215 |
216 | use Number::{Integer, Real};
217 |
218 | total = match (total, number) {
219 | // If the sum and new term are integers, use integers.
220 | (Integer(total), Integer(term)) => Integer(total + term),
221 | // Otherwise, if the either the total or new term are machine real numbers,
222 | // use floating point numbers.
223 | (Integer(int), Real(real)) | (Real(real), Integer(int)) => {
224 | Number::real(int as f64 + *real)
225 | },
226 | (Real(total), Real(term)) => Real(total + term),
227 | }
228 | }
229 |
230 | Expr::number(total)
231 | }
232 |
--------------------------------------------------------------------------------
/wolfram-library-link/src/async_tasks.rs:
--------------------------------------------------------------------------------
1 | //! Support for Wolfram Language asynchronous tasks.
2 | //!
3 | //! # Credits
4 | //!
5 | //! The implementations of this module and the associated examples are based on the path
6 | //! laid out by [this StackOverflow answer](https://mathematica.stackexchange.com/a/138433).
7 |
8 | use std::{
9 | ffi::{c_void, CString},
10 | panic,
11 | };
12 |
13 | use static_assertions::assert_not_impl_any;
14 |
15 | use crate::{rtl, sys, DataStore};
16 |
17 |
18 | /// Handle to a Wolfram Language [`AsynchronousTaskObject`][ref/AsynchronousTaskObject]WL
19 | /// instance.
20 | ///
21 | /// Use [`spawn_with_thread()`][AsyncTaskObject::spawn_with_thread] to spawn a new
22 | /// asynchronous task.
23 | ///
24 | /// [ref/AsynchronousTaskObject]: https://reference.wolfram.com/language/ref/AsynchronousTaskObject.html
25 | #[derive(Debug)]
26 | pub struct AsyncTaskObject(sys::mint);
27 |
28 | // TODO: Determine if it would be safe for this type to implement Copy/Clone.
29 | assert_not_impl_any!(AsyncTaskObject: Copy, Clone);
30 |
31 |
32 | //======================================
33 | // Impls
34 | //======================================
35 |
36 | impl AsyncTaskObject {
37 | /// Spawn a new Wolfram Language asynchronous task.
38 | ///
39 | /// This method can be used within a LibraryLink function that was called via
40 | ///
41 | /// ```wolfram
42 | /// Internal`CreateAsynchronousTask[
43 | /// _LibraryFunction,
44 | /// args_List,
45 | /// handler
46 | /// ]
47 | /// ```
48 | ///
49 | /// to create a new [`AsynchronousTaskObject`][ref/AsynchronousTaskObject]WL
50 | /// that uses a background thread that can generate events that will be processed
51 | /// asynchronously by the Wolfram Language.
52 | ///
53 | /// The background thread is given an `AsyncTaskObject` that has the same id as
54 | /// the `AsyncTaskObject` returned from this function. Events generated by the
55 | /// background thread using [`raise_async_event()`][AsyncTaskObject::raise_async_event]
56 | /// will result in an asynchronous call to the Wolfram Language `handler` function
57 | /// specified in the call to `` Internal`CreateAsynchronousEvent ``.
58 | ///
59 | /// [ref/AsynchronousTaskObject]: https://reference.wolfram.com/language/ref/AsynchronousTaskObject.html
60 | pub fn spawn_with_thread(f: F) -> Self
61 | where
62 | F: FnMut(AsyncTaskObject) + Send + panic::UnwindSafe + 'static,
63 | {
64 | spawn_async_task_with_thread(f)
65 | }
66 |
67 | /// Returns the numeric ID which identifies this async object.
68 | pub fn id(&self) -> sys::mint {
69 | let AsyncTaskObject(id) = *self;
70 | id
71 | }
72 |
73 | /// Returns whether this async task is still alive.
74 | ///
75 | /// *LibraryLink C Function:* [`asynchronousTaskAliveQ`][sys::st_WolframIOLibrary_Functions::asynchronousTaskAliveQ].
76 | pub fn is_alive(&self) -> bool {
77 | let is_alive: sys::mbool = unsafe { rtl::asynchronousTaskAliveQ(self.id()) };
78 |
79 | crate::bool_from_mbool(is_alive)
80 | }
81 |
82 | /// Returns whether this async task has been started.
83 | ///
84 | /// *LibraryLink C Function:* [`asynchronousTaskStartedQ`][sys::st_WolframIOLibrary_Functions::asynchronousTaskStartedQ].
85 | pub fn is_started(&self) -> bool {
86 | let is_started: sys::mbool = unsafe { rtl::asynchronousTaskStartedQ(self.id()) };
87 |
88 | crate::bool_from_mbool(is_started)
89 | }
90 |
91 | /// Raise a new named asynchronous event associated with the current async task.
92 | ///
93 | /// # Example
94 | ///
95 | /// Raise a new asynchronous event with no associated data:
96 | ///
97 | /// This will cause the Wolfram Language event handler associated with this task to
98 | /// be run.
99 | ///
100 | /// *LibraryLink C Function:* [`raiseAsyncEvent`][sys::st_WolframIOLibrary_Functions::raiseAsyncEvent].
101 | ///
102 | /// ```no_run
103 | /// use wolfram_library_link::{AsyncTaskObject, DataStore};
104 | ///
105 | /// let task_object: AsyncTaskObject = todo!();
106 | ///
107 | /// task_object.raise_async_event("change", DataStore::new());
108 | /// ```
109 | pub fn raise_async_event(&self, name: &str, data: DataStore) {
110 | let AsyncTaskObject(id) = *self;
111 |
112 | let name = CString::new(name)
113 | .expect("unable to convert raised async event name to CString");
114 |
115 | unsafe {
116 | // raise_async_event(id, name.as_ptr() as *mut c_char, data.into_ptr());
117 | rtl::raiseAsyncEvent(id, name.into_raw(), data.into_raw());
118 | }
119 | }
120 | }
121 |
122 | fn spawn_async_task_with_thread(task: F) -> AsyncTaskObject
123 | where
124 | // Note: Ensure that the bound on async_task_thread_trampoline() is kept up-to-date
125 | // with this bound.
126 | F: FnMut(AsyncTaskObject) + Send + 'static + panic::UnwindSafe,
127 | {
128 | // FIXME: This box is being leaked. Where is an appropriate place to drop it?
129 | let boxed_closure = Box::into_raw(Box::new(task));
130 |
131 | // Spawn a background thread using the user closure.
132 | let task_id: sys::mint = unsafe {
133 | rtl::createAsynchronousTaskWithThread(
134 | Some(async_task_thread_trampoline::),
135 | boxed_closure as *mut c_void,
136 | )
137 | };
138 |
139 | AsyncTaskObject(task_id)
140 | }
141 |
142 | unsafe extern "C" fn async_task_thread_trampoline(
143 | async_object_id: sys::mint,
144 | boxed_closure: *mut c_void,
145 | ) where
146 | F: FnMut(AsyncTaskObject) + Send + 'static + panic::UnwindSafe,
147 | {
148 | let boxed_closure: &mut F = &mut *(boxed_closure as *mut F);
149 |
150 | // static_assertions::assert_impl_all!(F: panic::UnwindSafe);
151 |
152 | // Catch any panics which occur.
153 | //
154 | // Use AssertUnwindSafe because:
155 | // 1) `F` is already required to implement UnwindSafe by the definition of AsyncTask.
156 | // 2) We don't introduce any new potential unwind safety with our minimal closure
157 | // here.
158 | match panic::catch_unwind(panic::AssertUnwindSafe(|| {
159 | boxed_closure(AsyncTaskObject(async_object_id))
160 | })) {
161 | Ok(()) => (),
162 | Err(_) => (),
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/wolfram-library-link/src/docs.rs:
--------------------------------------------------------------------------------
1 | //! Documentation articles about the usage of `wolfram-library-link`.
2 | //!
3 | //! ### How Tos
4 | //!
5 | //! * [How To: Convert Between Rust and Wolfram Types][converting_between_rust_and_wolfram_types]
6 | //! * [How To: Evaluate Wolfram code from Rust][evaluate_wolfram_code_from_rust]
7 |
8 | #![allow(unused_imports)]
9 |
10 | pub mod converting_between_rust_and_wolfram_types;
11 | pub mod evaluate_wolfram_code_from_rust;
12 |
--------------------------------------------------------------------------------
/wolfram-library-link/src/docs/converting_between_rust_and_wolfram_types.rs:
--------------------------------------------------------------------------------
1 | /*!
2 | # How To: Convert Between Rust and Wolfram Types
3 |
4 | ## Converting Rust types into Wolfram expressions
5 |
6 | Suppose you have a type in you Rust program that you want to convert into an equivalent
7 | Wolfram Language expression representation.
8 |
9 | For the purpose of this example, we'll assume the Rust structure that you want to
10 | convert into an expression is the following:
11 |
12 |
13 | ```rust
14 | struct Point {
15 | x: f64,
16 | y: f64
17 | }
18 | ```
19 |
20 | and that the desired Wolfram expression representation is a
21 | [`Point`](https://reference.wolfram.com/language/ref/Point.html):
22 |
23 | ```wolfram
24 | Point[{x, y}]
25 | ```
26 |
27 |
28 | There are two ways to perform this convertion using wolfram-library-link. Both involve
29 | transfering the expression using [WSTP](https://crates.io/crates/wstp), via the
30 | [`#[export(wstp)]`][crate::export#exportwstp] annotation.
31 |
32 | ### Method #1: Manual WSTP calls
33 |
34 | **Rust**
35 |
36 | ```rust
37 | # mod scope {
38 | */
39 | #![doc = include_str!("../../examples/docs/convert/manual_wstp.rs")]
40 | /*!
41 | # }
42 | ```
43 |
44 | **Wolfram**
45 |
46 | ```wolfram
47 | */
48 | #![doc = include_str!("../../RustLink/Examples/Docs/Convert/ManualWSTP.wlt")]
49 | /*!
50 | ```
51 |
52 | ### Method #2: Convert to [`Expr`]
53 |
54 | In this method, instead of passing our `Point[{x, y}]` expression incrementally using
55 | individual WSTP function calls, the `Point` expression is constructed using the [`Expr`]
56 | type.
57 |
58 | **Rust**
59 |
60 | ```rust
61 | # mod scope {
62 | */
63 | #![doc = include_str!("../../examples/docs/convert/using_expr.rs")]
64 | /*!
65 | # }
66 | ```
67 |
68 | **Wolfram**
69 |
70 | ```wolfram
71 | */
72 | #![doc = include_str!("../../RustLink/Examples/Docs/Convert/UsingExpr.wlt")]
73 | /*!
74 | ```
75 |
76 | ##
77 | */
78 |
79 | use crate::expr::Expr;
80 |
--------------------------------------------------------------------------------
/wolfram-library-link/src/docs/evaluate_wolfram_code_from_rust.rs:
--------------------------------------------------------------------------------
1 | /*!
2 | # How To: Evaluate Wolfram code from Rust
3 |
4 |
5 | ## Generating Wolfram messages from Rust
6 |
7 | Suppose you want to generate a Wolfram [`Message[..]`][ref/Message] from within Rust.
8 |
9 | The easiest way to accomplish this is to construct an appropriate Wolfram expression
10 | using the [`Expr`] type, and then use the [`evaluate()`] function to call back into
11 | Wolfram to evaluate that expression.
12 |
13 | [ref/Message]: https://reference.wolfram.com/language/ref/Message.html
14 |
15 | **Rust**
16 |
17 | ```rust
18 | # mod scope {
19 | */
20 | #![doc = include_str!("../../examples/docs/evaluate_wolfram_code_from_rust/generate_message.rs")]
21 | /*!
22 | # }
23 | ```
24 |
25 | **Wolfram**
26 |
27 | ```wolfram
28 | */
29 | #![doc = include_str!("../../RustLink/Examples/Docs/EvaluateWolframCodeFromRust/GenerateMessage.wlt")]
30 | /*!
31 | ```
32 |
33 | ## Using Print[..] from Rust
34 |
35 | *TODO*
36 |
37 | */
38 |
39 | use crate::{evaluate, expr::Expr};
40 |
--------------------------------------------------------------------------------
/wolfram-library-link/src/managed.rs:
--------------------------------------------------------------------------------
1 | //! Managed expressions.
2 | //!
3 | //! Managed expressions are Wolfram Language expressions created using
4 | //! [`CreateManagedLibraryExpression`][ref/CreateManagedLibraryExpression]WL,
5 | //! which are associated with a unique [`Id`] number that is shared with a loaded library.
6 | //!
7 | //! Using [`register_library_expression_manager()`], a library can register a callback
8 | //! function, which will recieve a [`ManagedExpressionEvent`] each time a new managed
9 | //! expression is created or deallocated.
10 | //!
11 | //! The managed expression [`Create(Id)`][ManagedExpressionEvent::Create] event is
12 | //! typically handled by the library to create an instance of some library data type that
13 | //! is associated with the managed expression. When the managed expression is finally
14 | //! deallocated, a [`Drop(Id)`][ManagedExpressionEvent::Drop] event is generated, and
15 | //! the library knows it is safe to free the associated data object.
16 | //!
17 | //! In this way, managed expressions allow memory-management of Rust objects to be
18 | //! performed indirectly based on the lifetime of a Wolfram Language expression.
19 | //!
20 | // TODO: Expand and polish this section: # Alternatives
21 | //
22 | // * Canonical WL expression representation
23 | // * MyStruct[] and manual memory management from WL
24 | //
25 | // Managed expressions are a way for objects that cannot be easily or efficiently
26 | // represented as a Wolfram Language expression to still be associated with the lifetime
27 | // of a Wolfram Language expression.
28 | //
29 | // If an object can be represented as a Wolfram expression, then that is the most
30 | // straightforward thing to do. the
31 | // simplest possible [[ToExpr, FromExpr]].
32 | //
33 | // The simplest alternative to managed expressions to simply convert the
34 | //
35 | //! # Related links
36 | //!
37 | //! * [Managed Library Expressions] section of the LibraryLink documentation.
38 | //! * The [wolfram-library-link managed expressions example](https://github.com/WolframResearch/wolfram-library-link-rs#example-programs).
39 | //!
40 | //! [Managed Library Expressions]: https://reference.wolfram.com/language/LibraryLink/tutorial/InteractionWithWolframLanguage.html#353220453
41 | //! [ref/CreateManagedLibraryExpression]: https://reference.wolfram.com/language/ref/CreateManagedLibraryExpression.html
42 |
43 | use std::{ffi::CString, sync::Mutex};
44 |
45 | use once_cell::sync::Lazy;
46 |
47 | use crate::{rtl, sys};
48 |
49 | /// Lifecycle events triggered by the creation and deallocation of managed expressions.
50 | pub enum ManagedExpressionEvent {
51 | /// Instruction that the library should create a new instance of a managed expression
52 | /// with the specified [`Id`].
53 | ///
54 | /// This event occurs when
55 | /// [CreateManagedLibraryExpression][ref/CreateManagedLibraryExpression] is called.
56 | ///
57 | /// [ref/CreateManagedLibraryExpression]: https://reference.wolfram.com/language/ref/CreateManagedLibraryExpression.html
58 | Create(Id),
59 | /// Instruction that the library should drop any data associated with the managed
60 | /// expression identified by this [`Id`].
61 | ///
62 | /// This event occurs when the managed expression is no longer used by the Wolfram
63 | /// Language.
64 | Drop(Id),
65 | }
66 |
67 | impl ManagedExpressionEvent {
68 | /// Get the managed expression [`Id`] targeted by this action.
69 | pub fn id(&self) -> Id {
70 | match *self {
71 | ManagedExpressionEvent::Create(id) => id,
72 | ManagedExpressionEvent::Drop(id) => id,
73 | }
74 | }
75 | }
76 |
77 | /// Unique identifier associated with an instance of a managed library expression.
78 | pub type Id = u32;
79 |
80 | /// Register a new callback function for handling managed expression events.
81 | pub fn register_library_expression_manager(
82 | name: &str,
83 | manage_instance: fn(ManagedExpressionEvent),
84 | ) {
85 | register_using_next_slot(name, manage_instance)
86 | }
87 |
88 | //======================================
89 | // C wrapper functions
90 | //======================================
91 |
92 | /// # Implementation note on the reason for this static / "slot" system.
93 | ///
94 | /// Having this static is not a direct requirement of the library expression
95 | /// C API, however it is necessary as a workaround for the problem described below.
96 | ///
97 | /// `registerLibraryExpressionManager()` expects a callback function of the type:
98 | ///
99 | /// ```ignore
100 | /// unsafe extern "C" fn(WolframLibraryData, mbool, mint)
101 | /// ```
102 | ///
103 | /// however, for the purpose of providing a more ergonomic and safe wrapper to the user,
104 | /// we want the user to be able to pass `register_library_expression_manager()` a callback
105 | /// function with the type:
106 | ///
107 | /// ```ignore
108 | /// fn(ManagedExpressionAction)
109 | /// ```
110 | ///
111 | /// This specific problem is an instance of the more general problem of how to expose a
112 | /// user-provided function/closure (non-`extern "C"`) as-if it actually were an
113 | /// `extern "C"` function.
114 | ///
115 | /// There are two common ways we could concievably do this:
116 | ///
117 | /// 1. Use a macro to generate an `extern "C"` function that calls the user-provided
118 | /// function.
119 | ///
120 | /// 2. Use a "trampoline" function (e.g. like async_task_thread_trampoline()) which has
121 | /// the correct `extern "C"` signature, and wraps the user function. This only works
122 | /// if the `extern "C"` function has a parameter that we can control and use to pass
123 | /// in a function pointer to the user-provided function.
124 | ///
125 | /// The (1.) strategy is easy to implement, but is undesirable because:
126 | ///
127 | /// a) it requires the user to use a macro -- and for subtle reasons (see: this comment)).
128 | /// b) it exposes the underlying `unsafe extern "C" fn(...)` type to the user.
129 | ///
130 | /// The (2.) strategy is often a good choice, but cannot be used in this particular
131 | /// case, because their is no way to pass a custom argument to the callback expected by
132 | /// registerLibraryExpressionManager().
133 | ///
134 | /// In both the (1.) and (2.) strategies, the solution is to create a single wrapper
135 | /// function for each user function, which is hard-coded to call the user function that
136 | /// it wraps.
137 | ///
138 | /// The technique used here is a third strategy:
139 | ///
140 | /// 3. Store the user-provided function pointer into a static array, and, instead of
141 | /// having a single `extern "C"` wrapper function, have multiple `extern "C"` wrapper
142 | /// functions, each of which statically access a different index in the static array.
143 | ///
144 | /// By using different `extern "C"` functions that access different static data, we
145 | /// can essentially "fake" having an extra function argument that we control.
146 | ///
147 | /// This depends on the observation that the callback function pointer is itself a
148 | /// value we control.
149 | ///
150 | /// This technique is limited by the fact that the static function pointers must be
151 | /// declared ahead of time (see `def_slot_fn!` below), and so practically there is a
152 | /// somewhat arbitrary limit on how many callbacks can be registered at a time.
153 | ///
154 | /// In our case, the *only* data we are able pass through the C API is the static function
155 | /// pointer we are registering; so strategy (3.) is the way to go.
156 | ///
157 | /// `SLOTS` has 8 elements, and we define 8 `extern "C" fn slot_(..)` functions that
158 | /// access only the corresponding element in `SLOTS`.
159 | ///
160 | /// 8 was picked arbitrarily, on the assumption that 8 different registered types should
161 | /// be sufficient for the vast majority of libraries. Libraries that want to register more
162 | /// than 8 types can use `rtl::registerLibraryExpressionManager` directly as a workaround.
163 | ///
164 | /// TODO: Also store the "name" of this manager, and pass it to the user function?
165 | static SLOTS: Lazy; 8]>> =
166 | Lazy::new(|| Mutex::new([None; 8]));
167 |
168 | fn register_using_next_slot(name: &str, manage_instance: fn(ManagedExpressionEvent)) {
169 | let name_cstr = CString::new(name).expect("failed to allocate C string");
170 |
171 | let mut slots = SLOTS.lock().unwrap();
172 |
173 | let available_slot: Option<(usize, &mut Option<_>)> = slots
174 | .iter_mut()
175 | .enumerate()
176 | .filter(|(_, slot)| slot.is_none())
177 | .next();
178 |
179 | let result = if let Some((index, slot)) = available_slot {
180 | *slot = Some(manage_instance);
181 | register_using_slot(name_cstr, index)
182 | } else {
183 | // Drop `slots` to avoid poisoning SLOTS when we panic.
184 | drop(slots);
185 | panic!("maxiumum number of library expression managers have been registered");
186 | };
187 |
188 | drop(slots);
189 |
190 | if let Err(()) = result {
191 | panic!(
192 | "library expression manager with name '{}' has already been registered",
193 | name
194 | );
195 | }
196 | }
197 |
198 | fn register_using_slot(name_cstr: CString, index: usize) -> Result<(), ()> {
199 | let static_slot_fn: unsafe extern "C" fn(_, _, _) = match index {
200 | 0 => slot_0,
201 | 1 => slot_1,
202 | 2 => slot_2,
203 | 3 => slot_3,
204 | 4 => slot_4,
205 | 5 => slot_5,
206 | 6 => slot_6,
207 | 7 => slot_7,
208 | 8 => slot_8,
209 | _ => unreachable!(),
210 | };
211 |
212 | let err_code: i32 = unsafe {
213 | rtl::registerLibraryExpressionManager(name_cstr.as_ptr(), Some(static_slot_fn))
214 | };
215 |
216 | if err_code != 0 {
217 | Err(())
218 | } else {
219 | Ok(())
220 | }
221 | }
222 |
223 | //--------------------------
224 | // Static slot_ functions
225 | //--------------------------
226 |
227 | fn call_callback_in_slot(slot: usize, mode: sys::mbool, id: sys::mint) {
228 | let slots = SLOTS.lock().unwrap();
229 |
230 | let user_fn: fn(ManagedExpressionEvent) = match slots[slot] {
231 | Some(func) => func,
232 | // TODO: Set something like "RustLink`$LibraryLastError" with a descriptive error?
233 | None => return,
234 | };
235 |
236 | // Ensure we're not holding a lock on `slots`, to avoid poisoning SLOTS in the case
237 | // `user_fn` panics.
238 | drop(slots);
239 |
240 | let id: u32 = match u32::try_from(id) {
241 | Ok(id) => id,
242 | // TODO: Set something like "RustLink`$LibraryLastError" with a descriptive error?
243 | Err(_) => return,
244 | };
245 |
246 | let action = match mode {
247 | 0 => ManagedExpressionEvent::Create(id),
248 | 1 => ManagedExpressionEvent::Drop(id),
249 | _ => panic!("unknown managed expression 'mode' value: {}", mode),
250 | };
251 |
252 | user_fn(action)
253 | }
254 |
255 | macro_rules! def_slot_fn {
256 | ($name:ident, $index:literal) => {
257 | unsafe extern "C" fn $name(
258 | // Assume this library is already initialized.
259 | _: sys::WolframLibraryData,
260 | mode: sys::mbool,
261 | id: sys::mint,
262 | ) {
263 | let result = crate::catch_panic::call_and_catch_panic(|| {
264 | call_callback_in_slot($index, mode, id)
265 | });
266 |
267 | if let Err(_) = result {
268 | // Do nothing.
269 | // TODO: Set something like "RustLink`$LibraryLastError" with this panic?
270 | }
271 | }
272 | };
273 | }
274 |
275 | def_slot_fn!(slot_0, 0);
276 | def_slot_fn!(slot_1, 1);
277 | def_slot_fn!(slot_2, 2);
278 | def_slot_fn!(slot_3, 3);
279 | def_slot_fn!(slot_4, 4);
280 | def_slot_fn!(slot_5, 5);
281 | def_slot_fn!(slot_6, 6);
282 | def_slot_fn!(slot_7, 7);
283 | def_slot_fn!(slot_8, 8);
284 |
--------------------------------------------------------------------------------
/wolfram-library-link/wolfram-library-link-macros/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wolfram-library-link-macros"
3 | version = "0.2.10"
4 | license = "MIT OR Apache-2.0"
5 | edition = "2021"
6 | repository = "https://github.com/WolframResearch/wolfram-library-link-rs"
7 | description = "Procedural macros used by wolfram-library-link"
8 |
9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10 |
11 | [lib]
12 | proc-macro = true
13 |
14 | [dependencies]
15 | proc-macro2 = "1.0.36"
16 | syn = { version = "1.0.85", features = ["full"] }
17 | quote = "1.0.14"
18 |
19 | [features]
20 | default = []
21 |
22 | automate-function-loading-boilerplate = []
23 |
--------------------------------------------------------------------------------
/wolfram-library-link/wolfram-library-link-macros/src/export.rs:
--------------------------------------------------------------------------------
1 | use proc_macro::TokenStream;
2 | use proc_macro2::TokenStream as TokenStream2;
3 |
4 | use quote::quote;
5 | use syn::{spanned::Spanned, Error, Ident, Item, Meta, NestedMeta};
6 |
7 | //======================================
8 | // #[wolfram_library_link::export]
9 | //======================================
10 |
11 | // NOTE: The comment below was written for the `export![]` declarative macro. Parts of
12 | // it are still relevent to the `#[export]` attribute procedural macro
13 | // implementation, but some of the constraints are not relevant any more.
14 | // I've kept it for context about what the tradeoffs in this functionality are.
15 |
16 | // # Design constraints
17 | //
18 | // The current design of this macro is intended to accommodate the following constraints:
19 | //
20 | // 1. Support automatic generation of wrapper functions without using procedural macros,
21 | // and with minimal code duplication. Procedural macros require external dependencies,
22 | // and can significantly increase compile times.
23 | //
24 | // 1a. Don't depend on the entire function definition to be contained within the
25 | // macro invocation, which leads to unergonomic rightward drift. E.g. don't
26 | // require something like:
27 | //
28 | // export![
29 | // fn foo(x: i64) { ... }
30 | // ]
31 | //
32 | // 1b. Don't depend on the entire function declaration to be repeated in the
33 | // macro invocation. E.g. don't require:
34 | //
35 | // fn foo(x: i64) -> i64 {...}
36 | //
37 | // export![
38 | // fn foo(x: i64) -> i64;
39 | // ]
40 | //
41 | // 2. The name of the function in Rust should match the name of the function that appears
42 | // in the WL LibraryFunctionLoad call. E.g. needing different `foo` and `foo__wrapper`
43 | // named must be avoided.
44 | //
45 | // To satisfy constraint 1, it's necessary to depend on the type system rather than
46 | // clever macro operations. This leads naturally to the creation of the `NativeFunction`
47 | // trait, which is implemented for all suitable `fn(..) -> _` types.
48 | //
49 | // Constraint 1b is unable to be met completely by the current implementation due to
50 | // limitations with Rust's coercion from `fn(A, B, ..) -> C {some_name}` to
51 | // `fn(A, B, ..) -> C`. The coercion requires that the number of parameters (`foo(_, _)`)
52 | // be made explicit, even if their types can be elided. If eliding the number of fn(..)
53 | // arguments were permitted, `export![foo]` could work.
54 | //
55 | // To satisfy constraint 2, this implementation creates a private module with the same
56 | // name as the function that is being wrapped. This is required because in Rust (as in
57 | // many languages), it's illegal for two different functions with the same name to exist
58 | // within the same module:
59 | //
60 | // ```
61 | // fn foo { ... }
62 | //
63 | // #[no_mangle]
64 | // pub extern "C" fn foo { ... } // Error: conflicts with the other foo()
65 | // ```
66 | //
67 | // This means that the export![] macro cannot simply generate a wrapper function
68 | // with the same name as the wrapped function, because they would conflict.
69 | //
70 | // However, it *is* legal for a module to contain a function and a child module that
71 | // have the same name. Because `#[no_mangle]` functions are exported from the crate no
72 | // matter where they appear in the module heirarchy, this offers an effective workaround
73 | // for the name clash issue, while satisfy constraint 2's requirement that the original
74 | // function and the wrapper function have the same name:
75 | //
76 | // ```
77 | // fn foo() { ... } // This does not conflict with the `foo` module.
78 | //
79 | // mod foo {
80 | // #[no_mangle]
81 | // pub extern "C" fn foo(..) { ... } // This does not conflict with super::foo().
82 | // }
83 | // ```
84 | pub(crate) fn export(
85 | attrs: syn::AttributeArgs,
86 | item: TokenStream,
87 | ) -> Result {
88 | //----------------------------------------------------
89 | // Parse the `#[export()]` attribute arguments.
90 | //----------------------------------------------------
91 |
92 | let ExportArgs {
93 | use_wstp,
94 | exported_name,
95 | hidden,
96 | } = parse_export_attribute_args(attrs)?;
97 |
98 | //--------------------------------------------------------------------
99 | // Validate that this attribute was applied to a `fn(..) { .. }` item.
100 | //--------------------------------------------------------------------
101 |
102 | let item: Item = syn::parse(item)?;
103 |
104 | let func = match item {
105 | Item::Fn(func) => func,
106 | _ => {
107 | return Err(Error::new(
108 | proc_macro2::Span::call_site(),
109 | "this attribute can only be applied to `fn(..) {..}` items",
110 | ));
111 | },
112 | };
113 |
114 | //-------------------------
115 | // Validate the user `func`
116 | //-------------------------
117 |
118 | // No `async`
119 | if let Some(async_) = func.sig.asyncness {
120 | return Err(Error::new(
121 | async_.span(),
122 | "exported function cannot be `async`",
123 | ));
124 | }
125 |
126 | // No generics
127 | if let Some(lt) = func.sig.generics.lt_token {
128 | return Err(Error::new(lt.span(), "exported function cannot be generic"));
129 | }
130 |
131 | //----------------------------
132 | // Create the output function.
133 | //----------------------------
134 |
135 | let name = func.sig.ident.clone();
136 | let exported_name: Ident = match exported_name {
137 | Some(name) => name,
138 | None => func.sig.ident.clone(),
139 | };
140 |
141 | let params = func.sig.inputs.clone();
142 |
143 | let wrapper = if use_wstp {
144 | export_wstp_function(&name, &exported_name, params, hidden)
145 | } else {
146 | export_native_function(&name, &exported_name, params.len(), hidden)
147 | };
148 |
149 | let output = quote! {
150 | // Include the users function in the output unchanged.
151 | #func
152 |
153 | #wrapper
154 | };
155 |
156 | Ok(output)
157 | }
158 |
159 | //--------------------------------------
160 | // #[export]: export NativeFunction
161 | //--------------------------------------
162 |
163 | fn export_native_function(
164 | name: &Ident,
165 | exported_name: &Ident,
166 | parameter_count: usize,
167 | hidden: bool,
168 | ) -> TokenStream2 {
169 | let params = vec![quote! { _ }; parameter_count];
170 |
171 | let mut tokens = quote! {
172 | mod #name {
173 | #[no_mangle]
174 | pub unsafe extern "C" fn #exported_name(
175 | lib: ::wolfram_library_link::sys::WolframLibraryData,
176 | argc: ::wolfram_library_link::sys::mint,
177 | args: *mut ::wolfram_library_link::sys::MArgument,
178 | res: ::wolfram_library_link::sys::MArgument,
179 | ) -> std::os::raw::c_int {
180 | // Cast away the unique `fn(...) {some_name}` function type to get the
181 | // generic `fn(...)` type.
182 | let func: fn(#(#params),*) -> _ = super::#name;
183 |
184 | ::wolfram_library_link::macro_utils::call_native_wolfram_library_function(
185 | lib,
186 | args,
187 | argc,
188 | res,
189 | func
190 | )
191 | }
192 | }
193 |
194 | };
195 |
196 | if !hidden && cfg!(feature = "automate-function-loading-boilerplate") {
197 | tokens.extend(quote! {
198 | // Register this exported function.
199 | ::wolfram_library_link::inventory::submit! {
200 | ::wolfram_library_link::macro_utils::LibraryLinkFunction::Native {
201 | name: stringify!(#exported_name),
202 | signature: || {
203 | let func: fn(#(#params),*) -> _ = #name;
204 | let func: &dyn ::wolfram_library_link::NativeFunction<'_> = &func;
205 |
206 | func.signature()
207 | }
208 | }
209 | }
210 | });
211 | }
212 |
213 | tokens
214 | }
215 |
216 | //--------------------------------------
217 | // #[export(wstp): export WstpFunction
218 | //--------------------------------------
219 |
220 | fn export_wstp_function(
221 | name: &Ident,
222 | exported_name: &Ident,
223 | parameter_tys: syn::punctuated::Punctuated,
224 | hidden: bool,
225 | ) -> TokenStream2 {
226 | // let params = vec![quote! { _ }; parameter_count];
227 |
228 | let mut tokens = quote! {
229 | mod #name {
230 | // Ensure that types imported into the enclosing parent module can be used in
231 | // the expansion of $argc. Always `Link` or `Vec` at the moment.
232 | use super::*;
233 |
234 | #[no_mangle]
235 | pub unsafe extern "C" fn #exported_name(
236 | lib: ::wolfram_library_link::sys::WolframLibraryData,
237 | raw_link: ::wolfram_library_link::wstp::sys::WSLINK,
238 | ) -> std::os::raw::c_int {
239 | // Cast away the unique `fn(...) {some_name}` function type to get the
240 | // generic `fn(...)` type.
241 | // The number of arguments is required for type inference of the variadic
242 | // `fn(..) -> _` type to work. See constraint 2a.
243 | let func: fn(#parameter_tys) -> _ = super::#name;
244 |
245 | // TODO: Why does this code work:
246 | // let func: fn(&mut _) = super::$name;
247 | // but this does not:
248 | // let func: fn(_) = super::$name;
249 |
250 | ::wolfram_library_link::macro_utils::call_wstp_wolfram_library_function(
251 | lib,
252 | raw_link,
253 | func
254 | )
255 | }
256 |
257 | }
258 | };
259 |
260 | if !hidden && cfg!(feature = "automate-function-loading-boilerplate") {
261 | tokens.extend(quote! {
262 | // Register this exported function.
263 | ::wolfram_library_link::inventory::submit! {
264 | ::wolfram_library_link::macro_utils::LibraryLinkFunction::Wstp { name: stringify!(#exported_name) }
265 | }
266 | });
267 | }
268 |
269 | tokens
270 | }
271 |
272 | //======================================
273 | // Parse `#[export()]` arguments
274 | //======================================
275 |
276 | /// Attribute arguments recognized by the `#[export(...)]` macro.
277 | struct ExportArgs {
278 | /// `#[export(wstp)]`
279 | use_wstp: bool,
280 | /// `#[export(name = "...")]`
281 | exported_name: Option,
282 | /// `#[export(hidden)]`
283 | ///
284 | /// If set, this exported function will not have an automatic loader entry generated
285 | /// for it.
286 | hidden: bool,
287 | }
288 |
289 | fn parse_export_attribute_args(attrs: syn::AttributeArgs) -> Result {
290 | let mut use_wstp = false;
291 | let mut hidden = false;
292 | let mut exported_name: Option = None;
293 |
294 | for attr in attrs {
295 | match attr {
296 | NestedMeta::Meta(ref meta) => match meta {
297 | Meta::Path(path) if path.is_ident("wstp") => {
298 | if use_wstp {
299 | return Err(Error::new(
300 | attr.span(),
301 | "duplicate export `wstp` attribute argument",
302 | ));
303 | }
304 |
305 | use_wstp = true;
306 | },
307 | Meta::Path(path) if path.is_ident("hidden") => {
308 | if hidden {
309 | return Err(Error::new(
310 | attr.span(),
311 | "duplicate export `hidden` attribute argument",
312 | ));
313 | }
314 |
315 | hidden = true;
316 | },
317 | Meta::List(_) | Meta::Path(_) => {
318 | return Err(Error::new(
319 | attr.span(),
320 | "unrecognized export attribute argument",
321 | ));
322 | },
323 | Meta::NameValue(syn::MetaNameValue {
324 | path,
325 | eq_token: _,
326 | lit,
327 | }) => {
328 | if path.is_ident("name") {
329 | if exported_name.is_some() {
330 | return Err(Error::new(
331 | attr.span(),
332 | "duplicate definition for `name`",
333 | ));
334 | }
335 |
336 | let lit_str = match lit {
337 | syn::Lit::Str(str) => str,
338 | _ => {
339 | return Err(Error::new(
340 | lit.span(),
341 | "expected `name = \"...\"`",
342 | ))
343 | },
344 | };
345 |
346 | exported_name = Some(
347 | lit_str
348 | .parse::()
349 | // Use the correct span for this error.
350 | .map_err(|err| Error::new(lit_str.span(), err))?,
351 | );
352 | } else {
353 | return Err(Error::new(
354 | path.span(),
355 | "unrecognized export attribute named argument",
356 | ));
357 | }
358 | },
359 | },
360 | NestedMeta::Lit(_) => {
361 | return Err(Error::new(
362 | attr.span(),
363 | "unrecognized export attribute literal argument",
364 | ));
365 | },
366 | }
367 | }
368 |
369 | Ok(ExportArgs {
370 | use_wstp,
371 | exported_name,
372 | hidden,
373 | })
374 | }
375 |
--------------------------------------------------------------------------------
/wolfram-library-link/wolfram-library-link-macros/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod export;
2 |
3 |
4 | use proc_macro::TokenStream;
5 | use proc_macro2::TokenStream as TokenStream2;
6 |
7 | use quote::quote;
8 | use syn::{spanned::Spanned, Error, Item};
9 |
10 | //======================================
11 | // #[wolfram_library_link::init]
12 | //======================================
13 |
14 | #[proc_macro_attribute]
15 | pub fn init(
16 | attr: proc_macro::TokenStream,
17 | item: proc_macro::TokenStream,
18 | ) -> proc_macro::TokenStream {
19 | match init_(attr.into(), item) {
20 | Ok(tokens) => tokens.into(),
21 | Err(err) => err.into_compile_error().into(),
22 | }
23 | }
24 |
25 | fn init_(attr: TokenStream2, item: TokenStream) -> Result {
26 | // Validate that we got `#[init]` and not `#[init(some, unexpected, arguments)]`.
27 | if !attr.is_empty() {
28 | return Err(Error::new(attr.span(), "unexpected attribute arguments"));
29 | }
30 |
31 | //--------------------------------------------------------------------
32 | // Validate that this attribute was applied to a `fn(..) { .. }` item.
33 | //--------------------------------------------------------------------
34 |
35 | let item: Item = syn::parse(item)?;
36 |
37 | let func = match item {
38 | Item::Fn(func) => func,
39 | _ => {
40 | return Err(Error::new(
41 | attr.span(),
42 | "this attribute can only be applied to `fn(..) {..}` items",
43 | ))
44 | },
45 | };
46 |
47 | //-------------------------
48 | // Validate the user `func`
49 | //-------------------------
50 |
51 | // No `async`
52 | if let Some(async_) = func.sig.asyncness {
53 | return Err(Error::new(
54 | async_.span(),
55 | "initialization function cannot be `async`",
56 | ));
57 | }
58 |
59 | // No generics
60 | if let Some(lt) = func.sig.generics.lt_token {
61 | return Err(Error::new(
62 | lt.span(),
63 | "initialization function cannot be generic",
64 | ));
65 | }
66 |
67 | // No parameters
68 | if !func.sig.inputs.is_empty() {
69 | return Err(Error::new(
70 | func.sig.inputs.span(),
71 | "initialization function should have zero parameters",
72 | ));
73 | }
74 |
75 | //--------------------------------------------------------
76 | // Create the output WolframLibrary_initialize() function.
77 | //--------------------------------------------------------
78 |
79 | let user_init_fn_name: syn::Ident = func.sig.ident.clone();
80 |
81 | let output = quote! {
82 | #func
83 |
84 | #[no_mangle]
85 | pub unsafe extern "C" fn WolframLibrary_initialize(
86 | lib: ::wolfram_library_link::sys::WolframLibraryData,
87 | ) -> ::std::os::raw::c_int {
88 | ::wolfram_library_link::macro_utils::init_with_user_function(
89 | lib,
90 | #user_init_fn_name
91 | )
92 | }
93 | };
94 |
95 | Ok(output)
96 | }
97 |
98 | //======================================
99 | // #[wolfram_library_link::export]
100 | //======================================
101 |
102 | #[proc_macro_attribute]
103 | pub fn export(
104 | attrs: proc_macro::TokenStream,
105 | item: proc_macro::TokenStream,
106 | ) -> proc_macro::TokenStream {
107 | let attrs: syn::AttributeArgs = syn::parse_macro_input!(attrs);
108 |
109 | match self::export::export(attrs, item) {
110 | Ok(tokens) => tokens.into(),
111 | Err(err) => err.into_compile_error().into(),
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/xtask/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xtask"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | clap = { version = "4.3.3", features = ["derive"] }
10 | bindgen = "^0.65.1"
11 | wolfram-app-discovery = "0.4.7"
--------------------------------------------------------------------------------
/xtask/src/main.rs:
--------------------------------------------------------------------------------
1 | //! `cargo xtask` helper commands for the wolfram-library-link-rs project.
2 | //!
3 | //! This crate follows the [`cargo xtask`](https://github.com/matklad/cargo-xtask)
4 | //! convention.
5 |
6 | use std::path::{Path, PathBuf};
7 |
8 | use clap::{Parser, Subcommand};
9 |
10 | use wolfram_app_discovery::{SystemID, WolframApp, WolframVersion};
11 |
12 | const BINDINGS_FILENAME: &str = "LibraryLink_bindings.rs";
13 |
14 | #[derive(Parser)]
15 | struct Cli {
16 | #[command(subcommand)]
17 | command: Commands,
18 | }
19 |
20 | #[derive(Subcommand)]
21 | enum Commands {
22 | /// Generate and save LibraryLink bindings for the current platform.
23 | GenBindings {
24 | /// Target to generate bindings for.
25 | #[arg(long)]
26 | target: Option,
27 | },
28 | }
29 |
30 | //======================================
31 | // Main
32 | //======================================
33 |
34 | fn main() {
35 | let Cli {
36 | command: Commands::GenBindings { target },
37 | } = Cli::parse();
38 |
39 | let app = WolframApp::try_default().expect("unable to locate default Wolfram app");
40 |
41 | let wolfram_version: WolframVersion =
42 | app.wolfram_version().expect("unable to get WolframVersion");
43 |
44 | let c_includes = app
45 | .library_link_c_includes_directory()
46 | .expect("unable to get LibraryLink C includes directory");
47 |
48 | let targets: Vec<&str> = match target {
49 | Some(ref target) => vec![target.as_str()],
50 | None => determine_targets().to_vec(),
51 | };
52 |
53 | println!("Generating bindings for: {targets:?}");
54 |
55 | for target in targets {
56 | generate_bindings(&wolfram_version, &c_includes, target);
57 | }
58 | }
59 |
60 | /// Generte bindings for multiple targets at once, based on the current
61 | /// operating system.
62 | fn determine_targets() -> &'static [&'static str] {
63 | if cfg!(target_os = "macos") {
64 | &["x86_64-apple-darwin", "aarch64-apple-darwin"]
65 | } else if cfg!(target_os = "windows") {
66 | &["x86_64-pc-windows-msvc"]
67 | } else if cfg!(target_os = "linux") {
68 | &["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"]
69 | } else {
70 | panic!("unsupported operating system for determining LibraryLink bindings target architecture")
71 | }
72 | }
73 |
74 | fn generate_bindings(wolfram_version: &WolframVersion, c_includes: &Path, target: &str) {
75 | // For the time being there is no reason this shouldn't be here.
76 | assert!(c_includes.ends_with("SystemFiles/IncludeFiles/C/"));
77 | assert!(c_includes.is_dir());
78 | assert!(c_includes.is_absolute());
79 |
80 | let clang_args = vec!["-target", target];
81 |
82 | let target_system_id = SystemID::try_from_rust_target(target)
83 | .expect("Rust target doesn't map to a known SystemID");
84 |
85 | #[rustfmt::skip]
86 | let bindings = bindgen::builder()
87 | .header(c_includes.join("WolframLibrary.h").display().to_string())
88 | .header(c_includes.join("WolframNumericArrayLibrary.h").display().to_string())
89 | .header(c_includes.join("WolframIOLibraryFunctions.h").display().to_string())
90 | .header(c_includes.join("WolframImageLibrary.h").display().to_string())
91 | .header(c_includes.join("WolframSparseLibrary.h").display().to_string())
92 | .generate_comments(true)
93 | .clang_arg("-fretain-comments-from-system-headers")
94 | .clang_arg("-fparse-all-comments")
95 | // .rustified_non_exhaustive_enum("MNumericArray_Data_Type")
96 | .constified_enum_module("MNumericArray_Data_Type")
97 | .constified_enum_module("MNumericArray_Convert_Method")
98 | .constified_enum_module("MImage_Data_Type")
99 | .constified_enum_module("MImage_CS_Type")
100 | .clang_args(clang_args)
101 | .generate()
102 | .expect("unable to generate Rust bindings to Wolfram LibraryLink using bindgen");
103 |
104 | let out_path: PathBuf = repo_root_dir()
105 | .join("wolfram-library-link-sys/generated/")
106 | .join(&wolfram_version.to_string())
107 | .join(target_system_id.as_str())
108 | .join(BINDINGS_FILENAME);
109 |
110 | std::fs::create_dir_all(out_path.parent().unwrap())
111 | .expect("failed to create parent directories for generating bindings file");
112 |
113 | bindings
114 | .write_to_file(&out_path)
115 | .expect("failed to write Rust bindings with IO error");
116 |
117 | println!("OUTPUT: {}", out_path.display());
118 | }
119 |
120 | fn repo_root_dir() -> PathBuf {
121 | let xtask_crate = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
122 | assert!(xtask_crate.file_name().unwrap() == "xtask");
123 | xtask_crate.parent().unwrap().to_path_buf()
124 | }
125 |
--------------------------------------------------------------------------------