wstp_example/
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
4use 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// ```
25wll::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)]
43fn 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)]
74fn 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)]
99fn 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)]
131fn 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)]
161fn 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)]
176fn 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<Expr>` argument list
195//======================================
196
197//------------------
198// total()
199//------------------
200
201#[wll::export(wstp)]
202fn total(args: Vec<Expr>) -> 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}