{"id":20119149,"url":"https://github.com/abridoux/booleanexpressionevaluation","last_synced_at":"2025-10-29T17:04:24.258Z","repository":{"id":53082410,"uuid":"224373513","full_name":"ABridoux/BooleanExpressionEvaluation","owner":"ABridoux","description":"Evaluate a string boolean expression with variables","archived":false,"fork":false,"pushed_at":"2021-04-07T13:59:52.000Z","size":2809,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-13T12:38:18.602Z","etag":null,"topics":["expression-evaluator","swift-package"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ABridoux.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-11-27T07:45:42.000Z","updated_at":"2023-06-09T10:27:13.000Z","dependencies_parsed_at":"2022-09-12T10:30:10.105Z","dependency_job_id":null,"html_url":"https://github.com/ABridoux/BooleanExpressionEvaluation","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ABridoux%2FBooleanExpressionEvaluation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ABridoux%2FBooleanExpressionEvaluation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ABridoux%2FBooleanExpressionEvaluation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ABridoux%2FBooleanExpressionEvaluation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ABridoux","download_url":"https://codeload.github.com/ABridoux/BooleanExpressionEvaluation/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241564447,"owners_count":19982958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["expression-evaluator","swift-package"],"created_at":"2024-11-13T19:14:36.920Z","updated_at":"2025-10-29T17:04:24.188Z","avatar_url":"https://github.com/ABridoux.png","language":"Swift","readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Swift-5.0+-f05138.svg?style=flat-square\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/iOS-12+-lightgrey.svg?style=flat-square\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/macOS-10.13+-lightgrey.svg?style=flat-square\" /\u003e\n    \u003ca href=\"https://swift.org/package-manager\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/swiftpm-compatible-brightgreen.svg?style=flat\" alt=\"Swift Package Manager\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n#  About boolean expressions\n\nThis library is useful to evaluate a string expression like `variable1 \u003e= 2 \u0026\u0026 variable2 == \"Value\"`. The variables are provided by a dictionary `[String : String]` representing the variables and their values. The complexity to evaluate a string expression is O(n)\n\n## Alternatives\n\nBoth [Expression](https://github.com/nicklockwood/Expression) from Nick Lockwood and [Eval](https://github.com/tevelee/Eval) from Lázló Teveli are interesintg alternatives. Exression is a ready to use framework and Lázló Teveli has produced a great work to deeply customize the usage of his framework. The goal of BooleanExpressionEvaluation is to focus on boolean expressions, when other expressions evaluation is not needed. Thus, the framework bears a little less complexity in its usage and customization.\n\n## Usage\n\n### Swift Package Manager\n\nAdd the package to your dependencies in your *Package.swift* file\n\n```swift\nlet package = Package (\n    ...\n    dependencies: [\n        .package(url: \"https://github.com/ABridoux/BooleanExpressionEvaluation.git\", from: \"1.0.0\")\n    ],\n    ...\n)\n```\n\nOr simply use Xcode menu *File* \u003e *Swift Packages* \u003e *Add Package Dependency*  and copy/paste the git URL: https://github.com/ABridoux/BooleanExpressionEvaluation.git\n\nThen import BooleanExpressionEvaluation in your file:\n\n```swift\nimport BooleanExpressionEvaluation\n```\n\n## Evaluate a String\n\nTo evaluate a String, create an `Expression`, passing the string as the parameter of the init. Note that the initialisation can throw an error, as the string expression can contain incorrect elements. You can then call the `evaluate()` function of the expression. This function can also throw an error, as the expression can have an incorrect grammar.\n\nFor example:\n\n(Note that the use of the raw string syntax `#\"\"#`  from Swift 5.0 is used to allow the use of double quotes without quoting them with `\\`)\n\n```swift\nlet variables = [\"userAge\": \"15\", \"userName\": \"Morty\"]\n\nlet stringExpression = #\"userAge \u003e 10 \u0026\u0026 userName == \"Morty\"\"#\nlet expression: Expression\ndo {\n    expression = try Expression(stringExpression)\n} catch {\n   // handle errors such as invalid elements in the expression\n   return\n}\n\ndo {\n   let result = try expression.evaluate(with: variables)\n   print(\"The user is allowed to travel across dimensions: \\(result)\")\n} catch {\n   // handle errors such as incorrect grammar in the expression\n}\n```\n\nYou can also create the `Expression` and evaluate it in the same `do{} catch{}` statement:\n\n```swift\nlet variables = [\"userAge\": \"15\", \"userName\": \"Morty\"]\n\nlet stringExpression = #\"userAge \u003e 10 \u0026\u0026 userName == \"Morty\"\"#\n\ndo {\n    let result = try Expression(stringExpression).evaluate(with: variables)\n} catch {\n   // handle errors such as invalid elements or incorrect grammar in the expression\n   return\n}\n```\n\nFinally, a simple use case is to implement an extension of `Expression` when you always evaluate them with a single source of variables, like a `VariablesManager` singleton in your overall project.\n\n```swift\nextension Expression {\n    func evaluate() throws -\u003e Bool {\n        return try evaluate(with: VariablesManager.shared.variables)\n    }\n}\n```\n\n\n## Operators\nThere are two types of operators available in an expression: comparison operators, to compare a variable and an other operand, and logic operators, to compare to boolean operands.\n\n### Default comparison operators\n- `==` for *equal*\n- `!=` for different\n- `\u003e` for *greater than*\n- `\u003e=` for *greater than or equal*\n- `\u003c` for *lesser than*\n- `\u003c=` for *lesser than or equal*\n- `isIn` The result is true is the left operand is contained the right one which is provided as a list of string values. The right operand has to be filled with values separated by commas. For example: if the variable `Ducks` has \"Riri, Fifi, Loulou\" for value, the comparison `'Riri' isIn Ducks ` is evaluated as true. It's possible to escape a comma with \"\\\".\n- `hasPrefix`\n- `hasSuffix`\n- `contains`: true when the left string contains the right string.\n- `matches`: true when the left operand matches the right operand, given as a regular expression.\n\n### Default logic operators\n- `\u0026\u0026` for *and*\n- `||` for *or*\n- `!`for *not* which works on single boolean and parenthesised expressions.\n\n### Custom operators\n\nYou can define custom operators in an extension of the `Operator` struct. Then add this operator to the `Operator.models` set. The same applies for the `LogicOperator` struct.\n\nFor example, you can define the `hasPrefix` operator (note that those operators already exist).\n\n```swift\nextension Operator {\n    public static var hasPrefix: Operator { Operator(\"hasPrefix\", isKeyword: true) { (lhs, rhs) in\n        guard let stringLhs = lhs as? String, let stringRhs = rhs as? String else {\n            throw ExpressionError.mismatchingType\n        }\n        return stringLhs.hasPrefix(stringRhs)\n    }}\n}\n```\nThen, in the setup of your app:\n\n```swift\nOperator.models.insert(.hasPrefix)\n```\n\nFinally, you can simply add an operator directly:\n\n```swift\nOperator.models.insert(Operator(\"~=\") { (lhs, rhs) in\n    guard let lhs = lhs as? String, let rhs = rhs as? String else { return nil }\n    return lhs.hasSuffix(rhs)\n})\n\n```\n\nYou can remove if you want the default operators, by calling the proper `Operator.removeFromModels(:)` method. You can also directly override the behavior of a default operator, by updating the `Operator.models`  set with an operator which has the same description.\n\n\u003cu\u003eNote\u003c/u\u003e\u003cbr\u003e\nAs it is not possible for now to restrict a closure signature to a protocol without specifying the type as generic in the structure, we cannot allow only `Comparable` operands in an operator `evaluate` closure. Nonetheless, only strings, boolean and double are allowed as operands in this framework now. Moreover, you might want to compare a double and a string, with an opeator like `count` for example. This would no be possible if the two operands were comparable with the same type.\n\n## Operands\n\nYou can compare a variable and an operand with a comparison operator. There are four types of operands.\n- `String` which are quoted with double quotes only\n- `Number` which group all numeric values, including floating ones\n- `Boolean` which are written as *true* or *false*\n- `Variables` which have to begin by a letter (lower or upper case) and can contain hyphens `-` and underscores `_`. You can compare two variables. Note that the boolean variables can be written without the `==` to evaluate if their state is `true`.\n\nGiven the following variables, here are some examples:\n\n#### Variables: \n- \"isUserAWizard\": \"true\"\n- \"userName\": \"Gandalf\"\n- \"userAge\": \"400\"\n- \"fellowship\": \"Gandalf, Frodo, Sam, Aragorn, Gimli, Legolas, Boromir, Merry, Pippin\"\n- \"hobbit\": \"Bilbo\"\n- \"passphrase\": \"You shall not pass!\"\n\n#### Expressions\n- `isUserAWizard == true \u0026\u0026 hobbit == \"Bilbo\"` → true\n- `userAge \u003e= 400 || userName != \"Saruman\"` → true\n- `fellowship \u003c: hobbit` → false\n- `userAge \u003c 400 \u0026\u0026 userName == \"Gandalf\"` → false\n- `(userAge \u003c 400 \u0026\u0026 userName == \"Gandalf) || fellowship \u003c: \"Aragorn\"` → true\n- `isUserAWizard \u0026\u0026 passphrase == \"You shall not pass!\"` → true\n\n### Variables\nVariables are provided to the `Expression` with a `[String: String]` dictionary. When comparing two operands with a comparison operator, one of the operands at least has to be a variable. Otherwise, the comparison expression result is already known and the expression do not need to be evaluated.\n\nA useful property of an `Expression` is `variables`, which is an array of the names of all the variables involved in the expression.\n\nThe default regular expression to match a variable is `[a-zA-Z]{1}[a-zA-Z0-9_-]+`. You can choose to use an other regular expression by providing it when initialising an expression:\n\n```swift\nlet expression = try? Expression(\"#variable \u003e= 2\", variablesRegexPattern: \"[a-zA-Z#]{1}[a-zA-Z0-9#]+\")\n```\nIf you always use the same regular expression, you should consider to write an extension of `Expression` to add the initialiser with this default expression. So with our last example:\n\n```swift\nextension Expression {\n    init(stringExpression: String) throws {\n        try self.init(stringExpression, variablesRegexPattern: \"[a-zA-Z#]{1}[a-zA-Z0-9#]+\")\n    }\n}\n```\n\n## Codable\n`Expression` implements the `Codable` protocol, and it is encoded/decoded as a `String`. Thus, you can try to decode a `String` value as an expression. And encoding it will render a `String`, describing it as a literal boolean expression. \n\n## Details about the internal logic\n\n### `ExpressionElement`\n\nRepresents an element of the expression, like  a variable, an operator or a number. There is four nested `Enum`s to group the different elements of an expression:\n- `ComparisonOperator` like `\u003e` or `=`  to evaluate a comparison between a variable and an other operand\n- `LogicOperator` to evaluate a result with two booleans\n- `Brackets`\n- `Operands` which always have an associated value, like a double, a boolean, a string or a variable\n\n### `Expression`\nAct like an array of `ExpressionElement`, although it is a `struct` which implements the `Collection` protocol. \n\n### `BooleanExpressionTokenizator`\n\nConverts an expression which contains comparison expressions to a boolean expression, which contains only logic operators, boolean operands and brackets.\n\n### `ExpressionEvaluator`\n\nUses the `BooleanExpressionTokenizator`  to get the different elements of the boolean expression and evaluate it. A new array is added into the `expressionResults` array of arrays each time an opening bracket is met. When a closing bracket is met, the last created array is reduced to a boolean which is then injected into the previous array.  The last created array is then deleted.\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabridoux%2Fbooleanexpressionevaluation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabridoux%2Fbooleanexpressionevaluation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabridoux%2Fbooleanexpressionevaluation/lists"}