diff --git a/README.md b/README.md index 3b6f893..24c5863 100644 --- a/README.md +++ b/README.md @@ -1,276 +1,389 @@ # bool-parser-java -A Boolean Expression Parser for Java -The library can help parse complex and nested boolean expressions. -The expressions are in SQL-like syntax, where you can use boolean operators and parentheses to combine individual expressions. +A Boolean Expression Parser for Java that helps parse and evaluate complex boolean and arithmetic expressions. -An expression can be as simple as `name = 'Sidhant'`. -A Complex expression is formed by combining these small expressions by logical operators and giving precedence using parenthesis +## Table of Contents -### Examples -#### Textual Equality +- [Overview](#overview) +- [Installation](#installation) +- [Basic Usage](#basic-usage) +- [Language Syntax Reference](#language-syntax-reference) + - [Data Types](#data-types) + - [Field References](#field-references) + - [Operators](#operators) + - [Expression Types](#expression-types) + - [Functions](#functions) + - [Advanced Usage](#advanced-usage) +- [Applications](#applications) + - [Boolean Expression Evaluator](#boolean-expression-evaluator) + - [Arithmetic Expression Evaluator](#arithmetic-expression-evaluator) +- [Node Types Post Parsing](#node-types-post-parsing) -Format: `${attributeName} = ${value}` +## Overview -Example: `name = 'john'` +This library helps parse and evaluate complex and nested boolean expressions. The expressions use a SQL-like syntax, where you can use boolean operators and parentheses to combine individual expressions. -#### Numeric Comparisons +An expression can be as simple as `name = 'Sidhant'` or as complex as `(age >= 18 AND age <= 65) AND (status = 'active' OR status = 'pending')`. -Format: `${attributeName} ${operator} ${value}` +## Installation -Example: `price > 12.99` +### Maven +```xml + + + com.github.sidhant92 + bool-parser-java + 2.0.0 + + +``` + +### Gradle +``` +dependencies { + implementation "com.github.sidhant92:bool-parser-java:2.0.0" +} +``` + +## Basic Usage -The ${value} must be numeric. Supported operators are `<`, `<=`, `=`, `!=`, `>=` and `>`, with the same semantics as in virtually all programming languages. +```java +final BoolParser boolParser = new BoolParser(); +final Try nodeOptional = boolParser.parseExpression("name = 'test'"); +``` -#### Numeric Range +There are two implementations for the parser: +- `BoolParser`: Standard implementation +- `CachedBoolParser`: Takes input for the max cache size to improve performance -Format: `${attributeName} ${lowerBound} TO ${upperBound}` +## Language Syntax Reference -Example: `price 5.99 TO 100` +### Data Types -`${lowerBound}` and `${upperBound}` must be numeric. Both are inclusive. +The language supports the following data types: -#### Boolean operators +1. **String**: Text values enclosed in single (`'`) or double (`"`) quotes. Examples: `'hello'`, `"world"` +2. **Integer**: Whole numbers without decimal points. Examples: `42`, `-10` +3. **Long**: Large whole numbers. Example: `1611473334114` +4. **Decimal**: Numbers with decimal points (stored as BigDecimal). Examples: `3.14`, `-2.5` +5. **Boolean**: `true` or `false` (case-insensitive) +6. **Version**: Semantic version numbers. Examples: `1.0.6`, `2.3.1.5` +7. **Null**: Represented as `null` -Example: +### Field References -`price < 10 AND (category:'Book' OR NOT category:'Ebook')` +Fields reference values in the data context: -Individual filters can be combined via boolean operators. The following operators are supported: +1. **Simple fields**: Direct reference to a field. Example: `age` +2. **Nested fields**: Access nested objects using dot notation. Example: `person.details.age` +3. **Default field**: When a default field is specified, operators can be used without explicitly naming the field. Example: `>= 18` (with "age" as default field) -* `OR`: must match any of the combined conditions (disjunction) -* `AND`: must match all of the combined conditions (conjunction) -* `NOT`: negate a filter +### Operators -Parentheses, `(` and `)`, can be used for grouping. +#### Comparison Operators -#### Usage Notes -* String must be enclosed either in single or double quotes. -* Variables substitution is supported by passing the name of the variable without the quotes. -* Phrases that includes quotes, like `content = "It's a wonderful day"` -* Phrases that includes quotes, like `attribute = 'She said "Hello World"'` -* For nested keys in data map you can use the dot notation, like `person.age` -* There are two implementations for the parser, Boolparser and CachedBoolParser. CachedBoolParser takes input the max cache size. +| Operator | Description | Example | +|----------|-------------|---------| +| `=` | Equal to | `name = 'John'` | +| `!=` | Not equal to | `status != 'pending'` | +| `>` | Greater than | `age > 18` | +| `>=` | Greater than or equal to | `price >= 9.99` | +| `<` | Less than | `temperature < 32` | +| `<=` | Less than or equal to | `quantity <= 100` | + +#### Logical Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `AND` or `&&` | Logical AND | `age > 18 AND status = 'active'` | +| `OR` or `\|\|` | Logical OR | `category = 'book' OR category = 'ebook'` | +| `NOT` or `not` | Logical NOT | `NOT (price < 10)` | + +#### Arithmetic Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `+` | Addition | `price + tax` | +| `-` | Subtraction | `total - discount` | +| `*` | Multiplication | `quantity * price` | +| `/` | Division | `total / count` | +| `%` | Modulus | `id % 10` | +| `^` | Exponent | `2 ^ 3` | + +### Expression Types + +#### Comparison Expressions + +Compare field values against literals or other fields: -## Usage -POM -```xml - - - com.github.sidhant92 - bool-parser-java - 2.0.0 - - -``` -Gradle ``` -dependencies { - implementation "com.github.sidhant92:bool-parser-java:2.0.0" -} +age > 21 +name = 'Alice' +price < maxPrice ``` +#### Logical Expressions + +Combine expressions with logical operators: -Code ``` -final BoolParser boolParser = new BoolParser(); -final Try nodeOptional = boolParser.parseExpression("name = 'test'"); +(age > 21) AND (status = 'active') +category = 'book' OR category = 'ebook' +NOT (price > 100) ``` -### Node Types Post Parsing -#### -NumericNode +#### Range Expressions + +Test if a value falls within a range (both bounds are inclusive): + +``` +age 18 TO 65 +price 9.99 TO 49.99 ``` -private final String field; -private final Object value; +#### Membership Expressions -private final Operator operator; +Test if a value is in a set: -private final DataType dataType; ``` +status IN ('pending', 'processing', 'shipped') +category NOT IN ('restricted', 'discontinued') +``` + +#### Array Operations + +Test if an array contains specific values: -#### -NumericRangeNode ``` -private final String field; +tags CONTAINS_ANY ('sale', 'new') +permissions CONTAINS_ALL ('read', 'write') +``` -private final Object fromValue; +### Functions -private final Object toValue; +#### Arithmetic Functions -private final DataType fromDataType; +| Function | Description | Example | +|----------|-------------|---------| +| `MIN` | Minimum value | `MIN(1, 2, 3)` or `MIN(numbers)` | +| `MAX` | Maximum value | `MAX(1, 2, 3)` or `MAX(numbers)` | +| `SUM` | Sum of values | `SUM(1, 2, 3)` or `SUM(numbers)` | +| `AVG` | Average of values | `AVG(1, 2, 3)` or `AVG(numbers)` | +| `MEAN` | Mean of values | `MEAN(1, 2, 3)` or `MEAN(numbers)` | +| `MEDIAN` | Median of values | `MEDIAN(1, 2, 3)` or `MEDIAN(numbers)` | +| `MODE` | Mode of values | `MODE(1, 1, 2, 3)` or `MODE(numbers)` | +| `LEN` | Length of string or array | `LEN('hello')` or `LEN(items)` | +| `INT` | Convert to integer | `INT(2.7)` or `INT(value)` | -private final DataType toDataType; -``` +Functions can be used in both boolean and arithmetic contexts: -#### -BooleanNode ``` -private Node left; +age > MIN(requiredAge, recommendedAge) +LEN(name) > 3 +``` -private Node right; +### Advanced Usage + +#### Combining Expressions + +Complex conditions can be created by combining expressions: -private LogicalOperationType operator; ``` +(age >= 18 AND age <= 65) AND (status = 'active' OR status = 'pending') +``` + +#### Arithmetic in Boolean Expressions + +Arithmetic expressions can be used within boolean expressions: -#### -UnaryNode ``` -private final DataType dataType; +price > (basePrice + tax) +age > (minimumAge + 5) +``` + +#### Function Nesting + +Functions can be nested: -private final Object value; ``` +MAX(1, 2, MIN(5, 7, 87)) +``` + +#### Null Checks + +Check if a field is null or not: -#### -FieldNode ``` -private final String field; +lastLogin = null +email != null ``` -#### -InNode +#### Working with Arrays + +Arrays can be used with specific operators and functions: + ``` -private final String field; +// Check if the 'roles' array contains any of the specified values +roles CONTAINS_ANY ('admin', 'editor') -private final List items; +// Check if the 'permissions' array contains all of the specified values +permissions CONTAINS_ALL ('read', 'write', 'delete') + +// Get the length of an array +LEN(items) > 0 ``` +#### String Handling -## Applications +- Strings must be enclosed in single or double quotes +- Quotes within strings can be handled: + - `content = "It's a wonderful day"` + - `attribute = 'She said "Hello World"'` -### Boolean Expression Evaluator +#### Case Sensitivity -The library can be used to evaluate a boolean expression. +Operators and function names are case-insensitive: + +``` +// These are equivalent +name = 'John' AND age > 21 +NAME = 'John' and AGE > 21 +``` -The following Data Types are supported: -1. String -2. Integer -3. Long -4. Decimal -5. Boolean -6. Semantic Version +However, field names and string values are case-sensitive. ---- -**NOTE** +## Applications -Decimal will internally use BigDecimal for storage. +### Boolean Expression Evaluator ---- +The library can be used to evaluate boolean expressions against a data context. Usage examples: -Simple Numerical Comparison -``` -final BooleanExpressionEvaluator booleanExpressionEvaluator = new BooleanExpressionEvaluator(new Boolparser()); +#### Simple Numerical Comparison +```java +final BooleanExpressionEvaluator booleanExpressionEvaluator = new BooleanExpressionEvaluator(new BoolParser()); final Map data = new HashMap<>(); data.put("age", 26); final Try resultOptional = booleanExpressionEvaluator.evaluate("age >= 27", data); -assertTrue(resultOptional.isPresent()); +assertTrue(resultOptional.isSuccess()); assertFalse(resultOptional.get()); ``` -Boolean Comparison -``` -final BooleanExpressionEvaluator booleanExpressionEvaluator = new BooleanExpressionEvaluator(new Boolparser()); + +#### Boolean Comparison +```java +final BooleanExpressionEvaluator booleanExpressionEvaluator = new BooleanExpressionEvaluator(new BoolParser()); final Map data = new HashMap<>(); data.put("age", 25); data.put("name", "sid"); final Try resultOptional = booleanExpressionEvaluator.evaluate("name = 'sid' AND age = 25", data); -assertTrue(resultOptional.isPresent()); +assertTrue(resultOptional.isSuccess()); assertTrue(resultOptional.get()); ``` -Nested Boolean Comparison -``` -final BooleanExpressionEvaluator booleanExpressionEvaluator = new BooleanExpressionEvaluator(new Boolparser()); + +#### Nested Boolean Comparison +```java +final BooleanExpressionEvaluator booleanExpressionEvaluator = new BooleanExpressionEvaluator(new BoolParser()); final Map data = new HashMap<>(); data.put("age", 25); data.put("name", "sid"); data.put("num", 45); -final Try resultOptional = booleanExpressionEvaluator.evaluate("name = sid AND (age = 25 OR num = 44)", data); -assertTrue(resultOptional.isPresent()); +final Try resultOptional = booleanExpressionEvaluator.evaluate("name = 'sid' AND (age = 25 OR num = 44)", data); +assertTrue(resultOptional.isSuccess()); assertTrue(resultOptional.get()); ``` -App Version Comparison -``` -final BooleanExpressionEvaluator booleanExpressionEvaluator = new BooleanExpressionEvaluator(new Boolparser()); + +#### App Version Comparison +```java +final BooleanExpressionEvaluator booleanExpressionEvaluator = new BooleanExpressionEvaluator(new BoolParser()); final Map data = new HashMap<>(); data.put("app_version", "1.5.9"); final Try resultOptional = booleanExpressionEvaluator.evaluate("app_version < 1.5.10", data); -assertTrue(resultOptional.isPresent()); +assertTrue(resultOptional.isSuccess()); assertTrue(resultOptional.get()); ``` The return type is `Try`. Failure means that parsing has failed and any fallback can be used. - [For a complete list of examples please check out the test file](src/test/java/com/github/sidhant92/boolparser/application/BooleanExpressionEvaluatorTest.java) - ### Arithmetic Expression Evaluator -The library can be used to evaluate a arithmetic expression. -It supports both numbers and variables which will be substituted from the passed data. -The passed variables can also be passed using the dot notation to access nested fields from the input data. - -The following Data Types are supported: -1. String -2. Integer -3. Long -4. Decimal - -The following Operators are supported: -1. Addition (+) -2. Subtraction (-) -3. Multiplication (*) -4. Division (/) -5. Modulus (%) -6. Exponent (^) - -The following functions are supported: -1. Minimum (min) -2. Maximum (max) -3. Average (avg) -4. Sum (sum) -5. Mean (mean) -6. Mode (mode) -7. Median (median) -8. Integer (int) - converts the input to integer -9. Length (len) - Returns length of the give array - -Syntax For using functions -Format: `${FunctionIdentifier} (item1, item2...)` - -Example: `min (1,2,3)` or with variable substitution `min (a,b,c)` +The library can also evaluate arithmetic expressions, supporting both literals and variables from the data context. -Usage examples: - -Simple Addition Operation -``` -final ArithmeticExpressionEvaluator evaluator = new ArithmeticExpressionEvaluator(new Boolparser()); +#### Simple Addition Operation +```java +final ArithmeticExpressionEvaluator evaluator = new ArithmeticExpressionEvaluator(new BoolParser()); final Map data = new HashMap<>(); data.put("a", 10); final Try resultOptional = evaluator.evaluate("a + 5", data); -assertTrue(resultOptional.isPresent()); -assertTrue(resultOptional.get(), 15); +assertTrue(resultOptional.isSuccess()); +assertEquals(resultOptional.get(), 15); ``` -Complex Arithmetic Operation -``` -final ArithmeticExpressionEvaluator evaluator = new ArithmeticExpressionEvaluator(new Boolparser()); +#### Complex Arithmetic Operation +```java +final ArithmeticExpressionEvaluator evaluator = new ArithmeticExpressionEvaluator(new BoolParser()); final Map data = new HashMap<>(); data.put("a", 10); final Try resultOptional = evaluator.evaluate("((5 * 2) + a) * 2 + (1 + 3 * (a / 2))", data); -assertTrue(resultOptional.isPresent()); -assertTrue(resultOptional.get(), 56); -``` -Function Usage +assertTrue(resultOptional.isSuccess()); +assertEquals(resultOptional.get(), 56); ``` -final ArithmeticExpressionEvaluator evaluator = new ArithmeticExpressionEvaluator(new Boolparser()); + +#### Function Usage +```java +final ArithmeticExpressionEvaluator evaluator = new ArithmeticExpressionEvaluator(new BoolParser()); final Map data = new HashMap<>(); data.put("a", 10); -final Try resultOptional = arithmeticExpressionEvaluator.evaluate("min (1,2,3)", data); +final Try resultOptional = evaluator.evaluate("min(1, 2, 3)", data); assertTrue(resultOptional.isSuccess()); assertEquals(resultOptional.get(), 1); ``` [For a complete list of examples please check out the test file](src/test/java/com/github/sidhant92/boolparser/application/ArithmeticExpressionEvaluatorTest.java) + +## Node Types Post Parsing + +The parser produces different node types based on the expression: + +### ComparisonNode +```java +private final Node left; +private final Node right; +private final Operator operator; +private final DataType dataType; +``` + +### NumericRangeNode +```java +private final String field; +private final Object fromValue; +private final Object toValue; +private final DataType fromDataType; +private final DataType toDataType; +``` + +### BooleanNode +```java +private Node left; +private Node right; +private LogicalOperationType operator; +``` + +### UnaryNode +```java +private final DataType dataType; +private final Object value; +``` + +### FieldNode +```java +private final String field; +``` + +### InNode +```java +private final String field; +private final List items; +```