uiua/lib.rs
1/*!
2The Uiua programming language
3
4This is the crate so you can use Uiua as a Rust library. If you just want to write programs in Uiua, you can check out [uiua.org](https://uiua.org) or the [GitHub repo](https://github.com/uiua-lang/uiua).
5
6# Usage
7
8The `uiua` crate is set up primarily to be installed as a binary. For this reason, when using it as a library, you'll likely want to disable default features.
9
10This disables some of the features that many users are used to having, so to re-enable things like regex and media encoding, you can enable the `batteries` feature.
11```toml
12# Cargo.toml
13[dependencies]
14uiua = { version = "*", default-features = false, features = ["batteries"] }
15```
16
17The main entry point is the [`Uiua`] struct, which is the Uiua runtime. It must be created with a [`SysBackend`]. [`Uiua::with_native_sys`] is a convenient way to create a Uiua runtime that uses the same backend as the Uiua CLI, though keep in mind it gives full access to the filesystem and TCP sockets and so probably shouldn't be used in a sandboxed environment.
18
19[`Value`] is the generic value type. It wraps one of five [`Array`] types.
20
21You can run Uiua code with [`Uiua::run_str`] or [`Uiua::run_file`].
22```rust
23use uiua::*;
24
25let mut uiua = Uiua::with_native_sys();
26uiua.run_str("&p + 1 2").unwrap();
27```
28You can push values onto the stack with [`Uiua::push`]. When you're done, you can get the results with [`Uiua::pop`], [`Uiua::take_stack`], or one of numerous pop+conversion convenience functions.
29```rust
30use uiua::*;
31
32let mut uiua = Uiua::with_native_sys();
33uiua.push(1);
34uiua.push(2);
35uiua.run_str("+").unwrap();
36let res = uiua.pop_int().unwrap();
37assert_eq!(res, 3);
38```
39
40Sometimes, you need to configure the compiler before running.
41
42You can create a new compiler with [`Compiler::new`]. Strings or files can be compiled with [`Compiler::load_str`] or [`Compiler::load_file`] respectively.
43You can get the compiled assembly with [`Compiler::finish`] and run it with [`Uiua::run_asm`].
44```rust
45use uiua::*;
46
47let mut comp = Compiler::new();
48comp.print_diagnostics(true);
49let asm = comp.load_str("+ 3 5").unwrap().finish();
50
51let mut uiua = Uiua::with_native_sys();
52uiua.run_asm(asm).unwrap();
53let res = uiua.pop_int().unwrap();
54assert_eq!(res, 8);
55```
56
57This can be shortened a bit with [`Uiua::compile_run`].
58```rust
59use uiua::*;
60
61let mut uiua = Uiua::with_native_sys();
62uiua.compile_run(|comp| {
63 comp.print_diagnostics(true).load_str("+ 3 5")
64});
65```
66
67You can create and bind Rust functions with [`Compiler::create_function`], [`Compiler::bind_function`], and [`Compiler::create_bind_function`]
68```rust
69use uiua::*;
70
71let mut comp = Compiler::new();
72comp.create_bind_function("MyAdd", (2, 1), |uiua| {
73 let a = uiua.pop_num()?;
74 let b = uiua.pop_num()?;
75 uiua.push(a + b);
76 Ok(())
77}).unwrap();
78comp.load_str("MyAdd 2 3").unwrap();
79let asm = comp.finish();
80
81let mut uiua = Uiua::with_native_sys();
82uiua.run_asm(asm).unwrap();
83let res = uiua.pop_num().unwrap();
84assert_eq!(res, 5.0);
85```
86
87Bindings can be retrieved with [`Uiua::bound_values`] or [`Uiua::bound_functions`].
88```rust
89use uiua::*;
90
91let mut uiua = Uiua::with_native_sys();
92uiua.run_str("
93 X ← 5
94 F ← +1
95").unwrap();
96
97let x = uiua.bound_values().remove("X").unwrap();
98assert_eq!(x.as_int(&uiua, None).unwrap(), 5);
99
100let f = uiua.bound_functions().remove("F").unwrap();
101let mut comp = Compiler::new().with_assembly(uiua.take_asm());
102comp.create_bind_function("AddTwo", (1, 1), move |uiua| {
103 uiua.call(&f)?;
104 uiua.call(&f)
105}).unwrap();
106comp.load_str("AddTwo 3").unwrap();
107uiua.run_asm(comp.finish()).unwrap();
108let res = uiua.pop_int().unwrap();
109assert_eq!(res, 5);
110```
111
112You can format Uiua code with the [`mod@format`] module.
113```rust
114use uiua::format::*;
115
116let input = "resh3_4rang12";
117let config = FormatConfig::default().with_trailing_newline(false);
118let formatted = format_str(input, &config).unwrap().output;
119assert_eq!(formatted, "↯3_4⇡12");
120```
121
122# Features
123
124The `uiua` crate has the following noteable feature flags:
125- `batteries`: Enables the following features:
126 - `regex`: Enables the `regex` function
127 - `image`: Enables image encoding and decoding
128 - `gif`: Enables GIF encoding and decoding
129 - `audio_encode`: Enables audio encoding and decoding
130- `native_sys`: Enables the [`NativeSys`] backend. This is the default backend used by the interpreter.
131- `audio`: Enables audio features in the [`NativeSys`] backend.
132- `https`: Enables the `&httpsw` system function
133- `invoke`: Enables the `&invk` system function
134- `trash`: Enables the `&ftr` system function
135- `raw_mode`: Enables the `&raw` system function
136*/
137
138#![allow(
139 unknown_lints,
140 clippy::single_match,
141 clippy::needless_range_loop,
142 clippy::mutable_key_type,
143 clippy::match_like_matches_macro,
144 mismatched_lifetime_syntaxes
145)]
146#![warn(missing_docs)]
147
148mod algorithm;
149mod array;
150mod assembly;
151mod boxed;
152mod check;
153mod compile;
154mod constant;
155mod cowslice;
156mod error;
157mod ffi;
158mod fill;
159pub mod format;
160mod function;
161mod grid_fmt;
162mod impl_prim;
163pub mod lsp;
164#[doc(hidden)]
165pub mod profile;
166mod run;
167mod run_prim;
168mod shape;
169#[cfg(feature = "stand")]
170#[doc(hidden)]
171pub mod stand;
172mod sys;
173mod tree;
174mod types;
175mod value;
176#[cfg(feature = "window")]
177#[doc(hidden)]
178pub mod window;
179
180#[allow(unused_imports)]
181pub use self::{
182 algorithm::{IgnoreError, media},
183 array::*,
184 assembly::*,
185 boxed::*,
186 compile::*,
187 constant::*,
188 error::*,
189 ffi::*,
190 function::*,
191 impl_prim::*,
192 lsp::{SpanKind, Spans},
193 run::*,
194 run_prim::*,
195 shape::*,
196 sys::*,
197 tree::*,
198 value::*,
199};
200#[doc(inline)]
201pub use uiua_parser::*;
202
203use self::algorithm::get_ops;
204
205/// The Uiua version
206pub const VERSION: &str = env!("CARGO_PKG_VERSION");
207
208/// A Uiua identifier
209pub type Ident = ecow::EcoString;
210
211fn is_default<T: Default + PartialEq>(v: &T) -> bool {
212 v == &T::default()
213}
214
215const _: () = {
216 assert!(
217 size_of::<usize>() >= size_of::<u32>(),
218 "Code requires that word size be at least four bytes"
219 );
220};
221
222#[cfg(test)]
223mod tests {
224 use std::{path::*, process::exit};
225
226 use uiua_parser::PrimClass;
227
228 use crate::{Compiler, Primitive, Uiua};
229
230 fn test_files(filter: impl Fn(&Path) -> bool) -> impl Iterator<Item = PathBuf> {
231 std::fs::read_dir("tests")
232 .unwrap()
233 .map(|entry| entry.unwrap().path())
234 .filter(move |path| {
235 path.is_file() && path.extension().is_some_and(|s| s == "ua") && filter(path)
236 })
237 }
238
239 #[test]
240 #[cfg(feature = "native_sys")]
241 fn suite() {
242 use super::*;
243 use std::thread;
244
245 let threads: Vec<_> = test_files(|path| {
246 !(path.file_stem().unwrap())
247 .to_string_lossy()
248 .contains("error")
249 })
250 .map(|path| {
251 thread::spawn(move || {
252 let code = std::fs::read_to_string(&path).unwrap();
253 // Test running
254 let mut env = Uiua::with_native_sys();
255 let mut comp = Compiler::with_backend(NativeSys);
256 if let Err(e) = comp
257 .load_str_src(&code, &path)
258 .and_then(|comp| env.run_asm(comp.asm.clone()))
259 {
260 panic!("Test failed in {}:\n{}", path.display(), e.report());
261 }
262 if let Some(diag) = comp
263 .take_diagnostics()
264 .into_iter()
265 .find(|d| d.kind > DiagnosticKind::Advice)
266 {
267 panic!("Test failed in {}:\n{}", path.display(), diag.report());
268 }
269 let (stack, under_stack) = env.take_stacks();
270 if !stack.is_empty() {
271 panic!("{} had a non-empty stack", path.display());
272 }
273 if !under_stack.is_empty() {
274 panic!("{} had a non-empty under stack", path.display());
275 }
276
277 // Make sure lsp spans doesn't panic
278 _ = Spans::from_input(&code);
279 })
280 })
281 .collect();
282 for thread in threads {
283 if let Err(e) = thread.join() {
284 if let Some(s) = e.downcast_ref::<String>() {
285 panic!("{s}");
286 } else {
287 panic!("{e:?}");
288 }
289 }
290 }
291 _ = std::fs::remove_file("example.ua");
292 }
293
294 #[test]
295 #[cfg(feature = "native_sys")]
296 fn error_dont_crash() {
297 use super::*;
298 let path = Path::new("tests_special/error.ua");
299 let mut code = std::fs::read_to_string(path).unwrap();
300 if code.contains('\r') {
301 code = code.replace('\r', "");
302 }
303 for section in code.split("\n\n") {
304 let mut env = Uiua::with_native_sys();
305 let mut comp = Compiler::new();
306 let res = comp
307 .load_str_src(section, path)
308 .and_then(|comp| env.run_asm(comp.finish()));
309 match res {
310 Ok(_) => {
311 if (comp.take_diagnostics().into_iter())
312 .filter(|diag| diag.kind > DiagnosticKind::Advice)
313 .count()
314 == 0
315 {
316 panic!(
317 "Test succeeded when it should have failed in {}:\n{}",
318 path.display(),
319 section
320 );
321 }
322 }
323 Err(e) => {
324 let message = e.to_string();
325 if message.contains("interpreter") {
326 panic!(
327 "Test resulted in an interpreter bug in {}:\n{}\n{}",
328 path.display(),
329 e.report(),
330 section
331 );
332 }
333 }
334 }
335 }
336 }
337
338 #[test]
339 #[cfg(feature = "native_sys")]
340 fn assembly_round_trip() {
341 use super::*;
342 let path = Path::new("tests_special/uasm.ua");
343 let mut comp = Compiler::with_backend(NativeSys);
344 comp.load_file(path).unwrap();
345 let asm = comp.finish();
346 let root = asm.root.clone();
347 let uasm = asm.to_uasm();
348 let asm = Assembly::from_uasm(&uasm).unwrap();
349 assert_eq!(asm.root, root);
350 let mut env = Uiua::with_native_sys();
351 env.run_asm(asm.clone()).unwrap();
352 // Run twice to make sure module caching works
353 env = Uiua::with_native_sys();
354 env.run_asm(asm).unwrap();
355 }
356
357 #[test]
358 fn lsp_spans() {
359 use super::*;
360 for path in test_files(|_| true) {
361 let code = std::fs::read_to_string(&path).unwrap();
362 Spans::from_input(&code);
363 }
364 }
365
366 #[test]
367 fn no_dbgs() {
368 if crate::compile::invert::DEBUG {
369 panic!("compile::invert::DEBUG is true");
370 }
371 if crate::compile::optimize::DEBUG {
372 panic!("compile::optimize::DEBUG is true");
373 }
374 if crate::compile::algebra::DEBUG {
375 panic!("compile::algebra::DEBUG is true");
376 }
377 }
378
379 #[test]
380 fn external_bind_before() {
381 let mut comp = Compiler::new();
382 comp.create_bind_function("F", (2, 1), |env| {
383 let a = env.pop_num().unwrap();
384 let b = env.pop_num().unwrap();
385 env.push(a + b);
386 Ok(())
387 })
388 .unwrap();
389 comp.load_str(
390 "F = |2 # External!\n\
391 F 1 2",
392 )
393 .unwrap();
394
395 let mut env = Uiua::with_native_sys();
396 env.run_compiler(&mut comp)
397 .unwrap_or_else(|e| panic!("{e}"));
398 let res = env.pop_int().unwrap();
399 assert_eq!(res, 3);
400 }
401
402 #[test]
403 fn external_bind_after() {
404 let mut comp = Compiler::new();
405 comp.load_str(
406 "F = |2 # External!\n\
407 F 1 2",
408 )
409 .unwrap();
410 comp.create_bind_function("F", (2, 1), |env| {
411 let a = env.pop_num().unwrap();
412 let b = env.pop_num().unwrap();
413 env.push(a + b);
414 Ok(())
415 })
416 .unwrap();
417
418 let mut env = Uiua::with_native_sys();
419 env.run_compiler(&mut comp)
420 .unwrap_or_else(|e| panic!("{e}"));
421 let res = env.pop_int().unwrap();
422 assert_eq!(res, 3);
423 }
424
425 #[test]
426 #[ignore] // Too expensive.
427 /// Run with:
428 /// ```
429 /// cargo t fuzz -- --nocapture --ignored
430 /// ```
431 fn fuzz() {
432 let iter = Primitive::non_deprecated().filter(|p| !matches!(p, Primitive::Sys(_)));
433 let arg_strs: Vec<_> = ((0..3).map(|n| {
434 ((0..=6).map(|i| {
435 let mut s = String::new();
436 for j in 0..i {
437 if j > 0 {
438 s.push(' ');
439 }
440 s.push('[');
441 for k in 0..n {
442 if k > 0 {
443 s.push(' ');
444 }
445 s.push_str(&(j * n + k).to_string());
446 }
447 s.push(']');
448 }
449 s
450 }))
451 .collect::<Vec<_>>()
452 }))
453 .collect();
454 for needs_name in [false, true] {
455 for a in
456 (iter.clone()).filter(|p| p.class() != PrimClass::Arguments || p.sig().is_none())
457 {
458 for b in iter.clone() {
459 for c in iter.clone() {
460 if a.glyph().is_none()
461 || b.glyph().is_none()
462 || c.glyph().is_none() != needs_name
463 {
464 continue;
465 }
466 if [a, c] == [Primitive::Repeat, Primitive::Infinity]
467 || [a, b] == [Primitive::Un, Primitive::Repeat]
468 || a == Primitive::Do
469 {
470 continue;
471 }
472 let arg_count = (a.sig().zip(b.sig()).zip(c.sig()))
473 .map(|((a, b), c)| c.compose(b.compose(a)).args())
474 .unwrap_or(4);
475 for args in &arg_strs {
476 let args = &args[arg_count];
477 let code = format!("{a}{b}{c} {args}");
478 eprintln!("{code}");
479 if let Err(e) = Uiua::with_safe_sys().run_str(&code)
480 && e.to_string().contains("The interpreter has crashed!")
481 {
482 exit(1);
483 }
484 }
485 }
486 }
487 }
488 }
489 }
490}