arithmetic_parser/
lib.rs

1//! Parser for arithmetic expressions with flexible definition of literals and support
2//! of type annotations.
3//!
4//! Overall, parsed grammars are similar to Rust syntax,
5//! [with a few notable differences](#differences-with-rust).
6//!
7//! # Supported syntax features
8//!
9//! - **Variables.** A variable name is defined similar to Rust and other programming languages,
10//!   as a sequence of alphanumeric chars and underscores that does not start with a digit.
11//! - **Literals.** The parser for literals is user-provided, thus allowing to apply the library
12//!   to different domains (e.g., finite group arithmetic).
13//! - `//` and `/* .. */` **comments**.
14//! - Basic **arithmetic operations**: `+`, `-` (binary and unary), `*`, `/`, `^` (power).
15//!   The parser outputs AST with nodes organized according to the operation priority.
16//! - **Function calls**: `foo(1.0, x)`.
17//! - **Parentheses** which predictably influence operation priority.
18//!
19//! The parser supports both complete and streaming (incomplete) modes; the latter is useful
20//! for REPLs and similar applications.
21//!
22//! ## Optional syntax features
23//!
24//! These features can be switched on or off when defining a [`Parse`](grammars::Parse) impl
25//! by declaring the corresponding [`Features`](grammars::Features).
26//!
27//! - **Tuples.** A tuple is two or more elements separated by commas, such as `(x, y)`
28//!   or `(1, 2 * x)`. Tuples are parsed both as lvalues and rvalues.
29//! - **Tuple destructuring.** Using a tuple as an lvalue, for example, `(x, y, z) = foo`.
30//!   The "rest" syntax is also supported, either named or unnamed: `(head, ...tail) = foo`,
31//!   `(a, ..., b, c) = foo`.
32//! - **Function definitions.** A definition looks like a closure definition in Rust, e.g.,
33//!   `|x| x - 10` or `|x, y| { z = max(x, y); (z - x, z - y) }`. A definition may be
34//!   assigned to a variable (which is the way to define named functions).
35//! - **Destructuring for function args.** Similar to tuple destructuring, it is possible to
36//!   destructure and group args in function definitions, for example, `|(x, y), ...zs| { }`.
37//! - **Blocks.** A block is several `;`-delimited statements enclosed in `{}` braces,
38//!   e.g, `{ z = max(x, y); (z - x, z - y) }`. The blocks can be used in all contexts
39//!   instead of a simple expression; for example, `min({ z = 5; z - 1 }, 3)`.
40//! - **Objects.** Object is a mapping of string fields to values. Objects are defined via
41//!   *object expressions*, which look similar to struct initialization in Rust or object
42//!   initialization in JavaScript; for example, `#{ x: 1, y }`. (Note the `#` char at the start
43//!   of the block; it is used to distinguish object expressions from blocks.)
44//! - **Methods.** Method call is a function call separated from the receiver with a `.` char
45//!   (e.g., `foo.bar(2, x)`).
46//! - **Type annotations.** A type annotation in the form `var: Type` can be present
47//!   in the lvalues or in the function argument definitions. The parser for type annotations
48//!   is user-defined.
49//! - **Boolean operations**: `==`, `!=`, `&&`, `||`, `!`.
50//! - **Order comparisons,** that is, `>`, `<`, `>=`, and `<=` boolean ops.
51//!
52//! ## Differences with Rust
53//!
54//! *(within shared syntax constructs; of course, Rust is much more expressive)*
55//!
56//! - No keyword for assigning a variable (i.e., no `let` / `let mut`). There are no
57//!   keywords in general.
58//! - Functions are only defined via the closure syntax.
59//! - There is "rest" destructuting for tuples and function arguments.
60//! - Type annotations are placed within tuple elements, for example, `(x: Num, _) = y`.
61//! - Object expressions are enclosed in `#{ ... }`, similarly to [Rhai](https://rhai.rs/).
62//! - Object field access and method calls accept arbitrary block-enclosed "names" in addition to
63//!   simple names. E.g., `xs.{Array.len}()` is a valid method call with `xs` receiver, `Array.len`
64//!   method name and an empty args list.
65//!
66//! # Crate features
67//!
68//! ## `std`
69//!
70//! *(On by default)*
71//!
72//! Enables support of types from `std`, such as the `Error` trait, and propagates to dependencies.
73//!
74//! ## `num-complex`
75//!
76//! *(Off by default)*
77//!
78//! Implements [`NumLiteral`](grammars::NumLiteral) for floating-point complex numbers
79//! (`Complex32` and `Complex64`).
80//!
81//! ## `num-bigint`
82//!
83//! *(Off by default)*
84//!
85//! Implements [`NumLiteral`](grammars::NumLiteral) for `BigInt` and `BigUint` from the `num-bigint` crate.
86//!
87//! # Examples
88//!
89//! Using a grammar for arithmetic on real values.
90//!
91//! ```
92//! # use assert_matches::assert_matches;
93//! use arithmetic_parser::{
94//!     grammars::{F32Grammar, Parse, Untyped},
95//!     NomResult, Statement, Expr, FnDefinition, LvalueLen,
96//! };
97//!
98//! const PROGRAM: &str = "
99//!     // This is a comment.
100//!     x = 1 + 2.5 * 3 + sin(a^3 / b^2 /* another comment */);
101//!     // Function declarations have syntax similar to Rust closures.
102//!     some_function = |a, b| (a + b, a - b);
103//!     other_function = |x| {
104//!         r = min(rand(), 0.5);
105//!         r * x
106//!     };
107//!     // Tuples and blocks are supported and have a similar syntax to Rust.
108//!     (y, z) = some_function({ x = x - 0.5; x }, x);
109//!     other_function(y - z)
110//! ";
111//!
112//! # fn main() -> anyhow::Result<()> {
113//! let block = Untyped::<F32Grammar>::parse_statements(PROGRAM)?;
114//! // First statement is an assignment.
115//! assert_matches!(
116//!     block.statements[0].extra,
117//!     Statement::Assignment { ref lhs, .. } if *lhs.fragment() == "x"
118//! );
119//! // The RHS of the second statement is a function.
120//! let some_function = match &block.statements[1].extra {
121//!     Statement::Assignment { rhs, .. } => &rhs.extra,
122//!     _ => panic!("Unexpected parsing result"),
123//! };
124//! // This function has a single argument and a single statement in the body.
125//! assert_matches!(
126//!     some_function,
127//!     Expr::FnDefinition(FnDefinition { ref args, ref body, .. })
128//!         if args.extra.len() == LvalueLen::Exact(2)
129//!             && body.statements.is_empty()
130//!             && body.return_value.is_some()
131//! );
132//! # Ok(())
133//! # }
134//! ```
135
136#![cfg_attr(not(feature = "std"), no_std)]
137#![doc(html_root_url = "https://docs.rs/arithmetic-parser/0.4.0-beta.1")]
138#![warn(missing_docs, missing_debug_implementations)]
139#![warn(clippy::all, clippy::pedantic)]
140#![allow(
141    clippy::missing_errors_doc,
142    clippy::must_use_candidate,
143    clippy::module_name_repetitions
144)]
145
146// Polyfill for `alloc` types.
147mod alloc {
148    #[cfg(not(feature = "std"))]
149    extern crate alloc as std;
150
151    pub(crate) use std::{boxed::Box, format, string::String, vec, vec::Vec};
152}
153
154pub use crate::{
155    ast::{
156        Block, Destructure, DestructureRest, Expr, ExprType, FnDefinition, Lvalue, LvalueLen,
157        LvalueType, ObjectDestructure, ObjectDestructureField, ObjectExpr, SpannedExpr,
158        SpannedLvalue, SpannedStatement, Statement, StatementType,
159    },
160    error::{Context, Error, ErrorKind, UnsupportedType},
161    ops::{BinaryOp, Op, OpPriority, UnaryOp},
162    parser::is_valid_variable_name,
163    spans::{with_span, InputSpan, LocatedSpan, Location, NomResult, Spanned},
164};
165
166mod ast;
167mod error;
168pub mod grammars;
169mod ops;
170mod parser;
171mod spans;