1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
//! libslide is the core of slide, implementing the end-to-end processing of a slide program. //! //! slide is described as a "static expression optimizer", which can be thought of as rougly equivalent //! to an "expression simplifier". Both of these terms are ambiguous ideas reasonable people can //! disagree on. For example, depending on the evaluation context, either `x ^ 2` or `x * x` may be //! "optimized" form of the same expression. //! //! slide uses reasonable (operation-reducing) simplification rules to achieve this goal. Recognizing the //! ambiguities described above, slide also hopes to make optimization customizable on the side of the //! user. //! //! This isn't the only design goal of slide; others include //! //! - Simplification as a platform, where optimization rules are configurable plugins. //! - Strong support for environments slide is most used in (e.g. LaTeX and ad-hoc interactive queries). //! - Easy integration with text editors and language servers. //! - Evaluation of mathematical statements even in ambiguous contexts. //! //! slide is still far from being complete. Contributions of any kind are warmly welcomed, as they help //! us progress in the achievement of these goals. //! //! The rest of this document discusses the architecture of slide. //! //! ## A brief overview of slide's architecture //! //! <img src="https://docs.google.com/drawings/d/e/2PACX-1vSguCnT1JCmJeF3NG7o1VYhp8Pqo4Qn093ysYcRRIR_KRVZWbrTALkD2pRPZRqLCZpxvSyuWraZFaUk/pub?w=864&h=432"> //! //! slide's design is very similar to a compiler front end, sans the type checking and ups the partial //! evaluation. //! //! The parser is a simple, hand-written RD parser designed to produce nice error messages. The parser //! module supports both expressions and [expression patterns](#expression-patterns). //! //! Given a parsed expression, the evaluator loops application of [string](#string-rules) and //! [function rules](#function-rules) until no rule further reduces the expression. This is the most //! interesting part of slide, which we discuss in the next section. //! //! Finally, the simplified expression is emitted. Currently slide supports a few emission strategies, //! and we are interested in adding more. //! //! ## Evaluator //! //! The `partial_evaluator` module loops the application of simplification rules on an expression until //! a rule no longer reduces an expression. //! //! ### Kinds of simplification rules //! //! slide supports "string" and "function" rules. String rules are [expression //! patterns](#expression-patterns) mappings that describe how the rule should be applied to an //! expression. For example, the string rule //! //! ```slide //! 1 * _a -> _a //! ``` //! //! says "1 times any expression `a` yields the expression `a`". String rules are applied to an //! expression by pattern matching the ASTs of the LHS of the string rule and the expression to be //! simplified. If the matching is successful, the matches are substituted on the RHS of the string //! rule. As an example, we apply the above rule on several expressions: //! //! ```slide //! 1 * (2 + v) -> (2 + v) //! 1 + (2 + v) -> no match! //! (2 + v) * 1 -> no match! //! ``` //! //! The fact that the third expression fails to match with the rule demonstrates one weakness of string //! rules. Another rule, `_a * 1 -> _a`, is needed to represent this case. (In practice, slide tries to //! permute the commutativity of an expression to induce the application of string rules written in a //! particular way, but this is not perfect). //! //! String rules also have no way to represent evaluation. Since they are purely mappings of terms, //! there is no way to describe the operation of addition with a string rule. //! //! Because of these limitations in string rules, slide also supports function rules. These are //! functions of the form //! //! ```ignore //! fn(expr: Expr) -> Option<Expr> //! ``` //! //! Function rules are much more powerful because they have access to an entire expression tree and can //! perform evaluations, but are responsible for their own pattern matching and more difficult to //! prove correctness for. //! //! As a summary, string rules are easy to write and inject into a slide program, but are very limited //! in their application. Function rules are more difficult to write and prove correct, but are much //! more flexible. //! //! #### Function rule evaluation modules //! //! slide-internal function rules may use a number of other modules to aid evaluation. We list the most //! common ones here: //! //! - `math`: the math module is a collection of algorithms used in the evaluation of an expression, like //! polynomial division. This module is often developed independently with the goal of eventual use //! in slide rules. The math module provides shims for converting between [expression representation](#expression-representation) //! and the data representations used by the math module's algorithms. //! - `flatten/fold`: this module tries to [normalize](#normalization) an expression with postconditions //! rules can rely on in their evaluation. //! - `roll/unroll`: these are utility functions that unroll an expression into a list of terms, or roll //! a list up into an expression AST. //! //! ### Normalization //! //! It is useful to normalize expressions in to a consistent form that can be expected by function rules //! (and to some extent string rules). The `flatten/fold` module provides expression normalization like //! combining like terms, normalizing similar operations (e.g. subtractions become additions), and other //! procedures whose postconditions rules can then rely on. //! //! Normalization is optional, and should be disabled when a user disables simplification rules used in //! the normalization process. //! //! ## Expression Representation //! //! Expressions are primarily represented as interned ASTs. For example, the expression "1 + 1" parses //! to an AST that looks like something like //! //! ```slide //! (+ <1>@common-1 <1>@common-1) //! ``` //! //! where `<1>@common-1` is the expression `1` held at the example pointer address `common-1`. //! //! The point here is that the common expression `1` is eliminated, and both references to `1` point to //! the same underlying expression. //! //! Because expressions are already held in boxed AST nodes, this interning process does not take an //! additional memory hit and provides several advantages, like skipping evaluation passes on identical //! expressions. //! //! ## Glossary //! //! #### String rules //! //! Simplification rules that describe the mapping of an [expression pattern](#expression-patterns) in a //! string form. For example, //! //! ```slide //! 1 * _a -> _a //! ``` //! //! is a string rule that describes the simplification "1 times any expression `a` yields the expression //! `a`". //! //! String rules are easy to write, but are strict in what they match and cannot express evaluation. //! //! #### Function rules //! //! Function rules are functions with the signature //! //! ```ignore //! fn(expr: Expr) -> Option<Expr> //! ``` //! //! Function rules are given an expression and can perform arbitrary evaluation to try to simplify the //! expression. Function rules are responsible for matching the expression themselves. //! //! Function rules are more difficult to write and prove correct, but are very flexible. //! //! #### Expressions //! //! Expressions are the inputs to a slide instance. For example, given the expression //! //! ```slide //! 2x + 3 + 4 + x //! ``` //! //! slide should emit the lowered expression `3x + 7`. //! //! #### Expression Patterns //! //! Expression patterns describe the shape of a expression. Expression patterns are used by [string rules](#string-rules) //! to describe how a rule should be applied. //! //! The syntax of expressions and expression patterns is nearly identical, but expression patterns have //! pattern metavariables rather than variables. The following patterns are available: //! //! | pattern | matches | //! |:--------- |:-------------- | //! | `#<name>` | A constant | //! | `$<name>` | A variable | //! | `_<name>` | Any expression | //! //! A metavariable can match exactly one expression. This means that if `_a` matches `1 + 2`, all other //! references to `_a` must match `1 + 2` as well. //! //! Some examples of expression patterns and matching expressions: //! //! | expression pattern | matches | //! | -- | -- | //! | `1 + #a` | `1 + 5.2` | //! | `#a + $a` | `10 + v` | //! | `_a / $a * _a` | `(9 + 1) / v * (9 + 1)` | //! | `_a + _b * $c * #d` | `(x ^ 2) + (5 / y) * w * 15` | #![deny(warnings)] #![deny(missing_docs)] #![doc( html_logo_url = "https://avatars2.githubusercontent.com/u/49662722?s=400&u=f0bcc9ee748892048588a2907eb3f176b91a1084&v=4" )] #[macro_use] mod grammar; pub use grammar::collectors; pub use grammar::visit; pub use grammar::{ Assignment, BinaryExpr, Expr, ExprPat, Grammar, InternedStr, RcExpr, RcExpression, Stmt, StmtKind, StmtList, UnaryExpr, }; mod common; pub use common::*; pub mod diagnostics; pub mod scanner; pub use scanner::scan; pub(crate) use scanner::ScanErrors; pub use scanner::ScanResult; pub use scanner::Token; mod parser; pub use parser::parse_expression_pattern; pub use parser::parse_statements; pub(crate) use parser::ParseErrors; pub use parser::ParseResult; mod linter; pub use linter::lint_expr_pat; pub use linter::lint_stmt; pub(crate) use linter::LintConfig; mod partial_evaluator; pub use partial_evaluator::build_rules; pub use partial_evaluator::evaluate; pub use partial_evaluator::evaluate_expr; pub use partial_evaluator::EvaluationResult; pub(crate) use partial_evaluator::PartialEvaluatorErrors; mod evaluator_rules; mod math; pub use math::Poly; // Since poly! is exposed, expose Poly too. pub(crate) mod emit; pub use emit::Emit; pub use emit::EmitConfig; pub use emit::EmitFormat; #[cfg(feature = "benchmark-internals")] pub use math::*; mod utils;