Skip to main content

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}