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}