Yaspar-IR
This crate provides a few representations of SMT scripts and other functionalities.
Introduction
A typical use of the crate
We could use this crate to parse and analyze SMTLib scripts. This can be achieved in the following workflow:
Checked v.s. Unchecked Building APIs
In general, we maintain a global top-level context to keep track of SMTLib objects and their validity:
let mut context = Context::new();
Via context, we can build typed SMTLib objects like Commands, Terms, and Sorts programmatically. Expectedly,
these
objects have well-formednes invariants. Users have two choices: either maintain the invariants manually via untyped
APIs,
or use the checked building APIs, which operations in a type-checking TC<T> monad.
To illustrate, the following snippet uses only the unchecked APIs to build (+ x y):
The code above is error-prone for the following reasons:
- it allocates the
Intsort, which might not exist in the current logic, e.g.QF_NRAincludes noInt. - Either
xorymight not be present in the current context, depending on the setup, as they require declarations.
As a result, the addition itself might not be well-formed. Therefore, this code, even though the assertion will still succeed, might not maintain the invariants.
However, since these APIs are low-level and thus efficient, advanced users could use them for efficient term building, provided that they are responsible for maintaining the invariants.
Instead, we could write the following snippet, which relies on checked APIs to maintain the invariants; hence users are relieved from the responsibility.
All checked APIs are in the form of typed_* for building terms, and wf_* for building sorts. See
the module ast::ctx::checked, and traits TypedApi and ScopedSortApi for the full list of APIs.
Analyzing hashconsed objects
Typed ASTs in this crate are hashconsed to optimize memory and run time efficiency. It is still possible to pattern
match on terms by calling .repr(). For example, the following function computes the depth of a given term:
More examples
More use of APIs can be found in the tests/ folder.
A parametric, algebraic representation of ASTs
For a package that provides functionalities for SMT scripts, sits at the core a parametric, algebraic representation of
ASTs.
This representation is defined by various enums and structs in raw::alg. Please consult the doc of the module for more
details.
This representation is flexible and allows two benefits:
-
We can instantiate this representation into different instances. In this package, there are two different instances of ASTs:
- an untyped representation with location information (
ast::u), and - a hashcons-ed, typed representation (
ast).
The former is a faithful parsing result of a grammatically correct SMT script, which could be potentially semantically mal-formed. Through type-checking (by calling
.type_check(), we convert an untyped AST to a typed AST, which is more compactly stored in memory via a hashconsing arena. If type-checking fails, the location information of the untyped representation is used for error reporting. - an untyped representation with location information (
-
We can achieve very good code reuse. In particular, functions implemented for the algebraic representation automatically works for all instantiations. For instance, the printing implementation works automatically for both typed and untyped representations. The type-checking algorithm also applies for both untyped (to obtain a typed AST) and typed (to re-check an AST constructed by untyped APIs) ASTs.
Use the macro instantiate_ast! to instantiate more variants!
Functionalities provided by the package
Currently, the crate provides the following functionalities:
- Parsing to an untyped representation: see functions exposed by
ast::u::UntypedAst. This functionality usesyasparunder the hood. - Typechecking: see
ast::Typecheck. After invoking.type_check(), we are handling the typed representation and can assume type invariants of the representation. All typed ASTs are managed byast::Context, which keeps track of the current logic, symbols, sorts, and cache. Type-checking commands has the side effect of potentially extending the context. - A set of unchecked APIs for building typed ASTs. This functionality is achieved using allocator functions exposed by
ast::Context. - A set of checked APIs for building typed ASTs. This functionality is exposed by the functions in
ast::ctx::checked. - Let-elimination: see
ast::LetElim. - Computing free variables of a given term: see
ast::FreeLocalVars. - A fresh variable allocator, which returns a fresh symbol that has not been used prior to the point of allocation: see
FreshVar. - A compact infrastructure for let-introduction based on topological sorting: see
ast::TopoLetIntro. This functionality introduces let-bindings to terms, so that they can be compactly printed with let-bindings inserted for sub-terms appearing multiple times. - Global and local substitutions; see
ast::Substitute,ast::GlobalSubstInplace, andast::GlobalSubstPreproc. - NNF and CNF conversion: see
ast::CNFConversion. This functionality requires the featurecnf. - Implicant computation: see
ast::FindImplicant. This functionality requires the featureimplicant-generation.
SMTLib compliance
This package is completely SMTLib-2.7-compliant. Namely, it follows the SMTLib spec and fully supports specified features (with exceptions below), including quantifiers and datatypes. Extension theories supported by z3 or cvc5 are usually not considered.
The following features are intensionally avoided, but we welcome contributors to extend them:
- floating points,
- higher order logic, and
- prenex polymorphism in user-defined sorts and functions.
Security
See CONTRIBUTING for more information.
License
This project is licensed under the Apache-2.0 License.