#expression-parser #boolean #bool #tagging

bool-tag-expr

Parse boolean expressions of tags for filtering and selecting

2 releases

0.1.0-beta.2 Mar 12, 2026
0.1.0-beta.1 Dec 3, 2025

#11 in #bool


Used in 7 crates

MIT license

48KB
859 lines

bool-tag-expr

This crate is still in beta release

Overview

This is the repo for the bool-tag-expr rust crate, which was created to improve fetching & filtering things by their tags. It facilitates parsing boolean expressions of tags into boolean expression trees that can be either transformed into SQL for selecting from a database, or evaluated against a list of entities to filter them.

Example

To select all British and French scientists who are not biologists, one can write (british | french) & scientist & !biologist. Using the crate this can be transformed into the SQL needed to select matching entries out of a database. For complete & working examples please visit docs.rs.


lib.rs:

About

This crate allows boolean expressions of tags to be written, parsed, and evaluated (e.g. converted to SQL for selecting out of a database). The tags can be restricted to the more typical single-value kind, but support is also supplied for key-value tags - this is achieved by making the tag name optional.

For example, to get all British and American scientists who are not chemists (see below for a proper example in Rust) one could use:

((nationality=american & scientist) | (=british & scientist)) & !chemist & person

The input requirements aren't rigid and so to get the same result we could also write, say:

(nationality=american | =british) & scientist & !chemist & person

Where:

  • nationality=american means entities with the a tag whose name is nationality and whose value is american
  • =british is the same as british and means entities with a tag value of british
  • !chemist means entities that do not have the tag value chemist
  • person means those entities that have a tag value of person

Syntax

  • & for AND (&& is a lexical error)
  • | for OR (|| is a lexical error)
  • ( and ) for logical grouping ()( is a syntax error)
  • ! for NOT
  • tag-name=tag-value, =tag-value, and tag-value are all valid tags

Parsing

A valid boolean expression must contain at least 1 tag.

This modules provides functionality for both:

  1. String (or custom type with necessary trait) → Bool expression tree
  2. String (or custom type with necessary trait) → Lexical token stream → Bool expression tree

See below for examples and clarification.

Examples

Basic

The simplest way to use this crate is to parse some boolean tag expression string and to then select items out of a database. Here is how to do that:

use bool_tag_expr::BoolTagExpr;
use bool_tag_expr::DbTableInfo;

// The boolean tag expression
let expr_str = "(nationality=american | =british) & scientist & !chemist & person";

// Parse it, returning early if there is an error
let expr_tree = BoolTagExpr::from(expr_str)?;

// Setup the database info
let table_name = "entity_tags";
let id_column = "entity_id";
let tag_name_column = "name";
let tag_value_column = "value";
let table_info = DbTableInfo::from(table_name, id_column, tag_name_column, tag_value_column)?;

// Generate the SQL using the expression tree and the database info
let bool_expr_sql_str = expr_tree.to_sql(&table_info);

// Create the full SQL statement
let sql = format!(
    r#"
        SELECT DISTINCT {id_column}
        FROM ({bool_expr_sql_str})
        LIMIT ?
    "#
);

Advanced

Should you want to get a BoolTagExpr from some type that isn't readily converted into a string, you can implement BoolTagExprLexicalParse for it. You will then automatically get an implementation of BoolTagExprSyntaxParse. You can then use BoolTagExpr::from(my_var)?; as above. Alternatively, you can lexically parse and then syntactically parse any type implementing these 2 traits (already implemented for strings). This might be helpful if, for example, you want to limit the number of tags in a boolean expression:

use bool_tag_expr::BoolTagExprSyntaxParse;
use bool_tag_expr::BoolTagExprLexicalParse;
use bool_tag_expr::Token;

// Get the boolean expression (e.g. from user input)
let expr = "(german | british) & poet";

// Parse (lexical only) the boolean expression
let tokens = expr.lexical_parse().unwrap_or_else(|error| {
    eprintln!("Lexical parse error: {error}");
    std::process::exit(1);
});

// Count the number of tags
let tag_count = tokens
    .tokens()
    .iter()
    .filter(|token| if let Token::Tag(_) = token { true } else { false })
    .count();

// Panic if there are too many tags
if tag_count > 5 {
    panic!()
}

// Parse (syntax) the tokens
let expr_tree = expr.syntax_parse().unwrap_or_else(|error| {
    eprintln!("Syntax parse error: {error}");
    std::process::exit(1);
});

Crate Features

There is only 1 optional feature: sqlx. It is not selected by default. Unless you are planning on compiling parts of the crate to WASM, it is recommended that you use the sqlx feature. If you are planning on pulling data out of a database, you must use this feature.

Warnings

For the NOT functionality to work correctly for all entires, every entry must have at least 1 tag.

Dependencies

~1–4MB
~57K SLoC