From 250047df0d70be19951f8fc14fb8493c020a01a9 Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Fri, 26 Jul 2024 11:25:01 +0200
Subject: [PATCH 01/11] feat: add package logical-expressions
This is a new package to evaluate logical expressions.
re #307
---
packages/logical-expressions/LICENSE | 21 ++++
packages/logical-expressions/README.md | 45 ++++++++
.../logical-expressions/build.tsconfig.json | 9 ++
packages/logical-expressions/package.json | 40 +++++++
packages/logical-expressions/rollup.config.ts | 22 ++++
packages/logical-expressions/src/evaluate.ts | 107 ++++++++++++++++++
packages/logical-expressions/src/index.ts | 4 +
packages/logical-expressions/src/tokenize.ts | 10 ++
.../logical-expressions/tests/index.test.ts | 68 +++++++++++
packages/logical-expressions/tsconfig.json | 4 +
packages/logical-expressions/typedoc.json | 3 +
yarn.lock | 11 ++
12 files changed, 344 insertions(+)
create mode 100644 packages/logical-expressions/LICENSE
create mode 100644 packages/logical-expressions/README.md
create mode 100644 packages/logical-expressions/build.tsconfig.json
create mode 100644 packages/logical-expressions/package.json
create mode 100644 packages/logical-expressions/rollup.config.ts
create mode 100644 packages/logical-expressions/src/evaluate.ts
create mode 100644 packages/logical-expressions/src/index.ts
create mode 100644 packages/logical-expressions/src/tokenize.ts
create mode 100644 packages/logical-expressions/tests/index.test.ts
create mode 100644 packages/logical-expressions/tsconfig.json
create mode 100644 packages/logical-expressions/typedoc.json
diff --git a/packages/logical-expressions/LICENSE b/packages/logical-expressions/LICENSE
new file mode 100644
index 000000000..8ef16f7a5
--- /dev/null
+++ b/packages/logical-expressions/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Ethereum Foundation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/logical-expressions/README.md b/packages/logical-expressions/README.md
new file mode 100644
index 000000000..d67bf9e22
--- /dev/null
+++ b/packages/logical-expressions/README.md
@@ -0,0 +1,45 @@
+
+
+ Logical Expressions
+
+ A library to evaluate logical expressions.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+| This library facilitates the work with logical expressions. It allows you to tokenize and evaluate logical expressions. |
+| ----------------------------------------------------------------------------------------------------------------------- |
diff --git a/packages/logical-expressions/build.tsconfig.json b/packages/logical-expressions/build.tsconfig.json
new file mode 100644
index 000000000..09b37d986
--- /dev/null
+++ b/packages/logical-expressions/build.tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "declarationDir": "dist/types",
+ "resolveJsonModule": true
+ },
+ "include": ["src"]
+}
diff --git a/packages/logical-expressions/package.json b/packages/logical-expressions/package.json
new file mode 100644
index 000000000..8b616adbf
--- /dev/null
+++ b/packages/logical-expressions/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "@zk-kit/logical-expressions",
+ "version": "1.0.0",
+ "description": "A library to evaluate logical expressions.",
+ "type": "module",
+ "license": "MIT",
+ "main": "dist/index.js",
+ "types": "dist/types/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/types/index.d.ts",
+ "require": "./dist/index.cjs",
+ "default": "./dist/index.js"
+ }
+ },
+ "files": [
+ "dist/",
+ "src/",
+ "LICENSE",
+ "README.md"
+ ],
+ "repository": "git@github.com:privacy-scaling-explorations/zk-kit.git",
+ "homepage": "https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/logical-expressions",
+ "bugs": {
+ "url": "https://github.com/privacy-scaling-explorations/zk-kit.git/issues"
+ },
+ "scripts": {
+ "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
+ "prepublishOnly": "yarn build"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "devDependencies": {
+ "@rollup/plugin-typescript": "^11.1.6",
+ "rimraf": "^5.0.5",
+ "rollup": "^4.12.0",
+ "rollup-plugin-cleanup": "^3.2.1"
+ }
+}
diff --git a/packages/logical-expressions/rollup.config.ts b/packages/logical-expressions/rollup.config.ts
new file mode 100644
index 000000000..0cb580ebc
--- /dev/null
+++ b/packages/logical-expressions/rollup.config.ts
@@ -0,0 +1,22 @@
+import typescript from "@rollup/plugin-typescript"
+import fs from "fs"
+import cleanup from "rollup-plugin-cleanup"
+
+const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"))
+const banner = `/**
+ * @module ${pkg.name}
+ * @version ${pkg.version}
+ * @file ${pkg.description}
+ * @copyright Ethereum Foundation ${new Date().getFullYear()}
+ * @license ${pkg.license}
+ * @see [Github]{@link ${pkg.homepage}}
+*/`
+
+export default {
+ input: "src/index.ts",
+ output: [
+ { file: pkg.exports["."].require, format: "cjs", banner },
+ { file: pkg.exports["."].default, format: "es", banner }
+ ],
+ plugins: [typescript({ tsconfig: "./build.tsconfig.json" }), cleanup({ comments: "jsdoc" })]
+}
diff --git a/packages/logical-expressions/src/evaluate.ts b/packages/logical-expressions/src/evaluate.ts
new file mode 100644
index 000000000..2d1a547f9
--- /dev/null
+++ b/packages/logical-expressions/src/evaluate.ts
@@ -0,0 +1,107 @@
+/**
+ * Function to determine the precedence of operators.
+ * Unary operators have higher precedence.
+ * Binary operators have the same precedence.
+ * @param op Operator to check the precedence.
+ * @returns The precedence of the operator.
+ */
+function precedence(op: string): number {
+ if (op === "not") return 2 // Highest precedence
+ if (op === "and") return 1
+ if (op === "xor") return 1
+ if (op === "or") return 1 // Lowest precedence
+ return 0
+}
+
+/**
+ * Function to apply the operator to one or two boolean values.
+ * @param op Operator to apply.
+ * @param a First boolean value.
+ * @param b Second boolean value.
+ * @returns The boolean value after applying the operator.
+ */
+function applyOp(op: string, a: boolean, b?: boolean): boolean {
+ switch (op) {
+ case "and":
+ return a && b!
+ case "or":
+ return a || b!
+ case "not":
+ return !a
+ default: // Case XOR
+ return a !== b! // XOR returns true if only one of the operands is true
+ }
+}
+
+/**
+ * Function to evaluate the tokenized expression.
+ * This algorithm is an adaptation of the
+ * {@link https://en.wikipedia.org/wiki/Shunting_yard_algorithm | Shunting Yard}
+ * algorithm to evaluate expressions with logical operators.
+ * @param tokens Tokens of the expression.
+ * The tokens can be boolean values, operators or parentheses.
+ * They represent a valid expression.
+ * @returns The boolean value after evaluating the expression.
+ */
+export default function evaluate(tokens: string[]): boolean {
+ const values: boolean[] = [] // Stack to store boolean values
+ const ops: string[] = [] // Stack to store operators
+
+ for (let i = 0; i < tokens.length; i += 1) {
+ const token = tokens[i]
+
+ if (token === "true") {
+ values.push(true)
+ } else if (token === "false") {
+ values.push(false)
+ } else if (token === "(") {
+ ops.push(token)
+ } else if (token === ")") {
+ // Evaluate the expression inside the parentheses
+ while (ops.length > 0 && ops[ops.length - 1] !== "(") {
+ const op = ops.pop()!
+ if (op === "not") {
+ const val = values.pop()!
+ values.push(applyOp(op, val))
+ } else {
+ const b = values.pop()!
+ const a = values.pop()!
+ values.push(applyOp(op, a, b))
+ }
+ }
+ ops.pop() // Remove '(' from stack
+ } else if (["and", "or", "not", "xor"].includes(token)) {
+ // Handle operators and ensure the correct precedence
+ while (ops.length > 0 && precedence(ops[ops.length - 1]) >= precedence(token)) {
+ const op = ops.pop()!
+ if (op === "not") {
+ const val = values.pop()!
+ values.push(applyOp(op, val))
+ } else {
+ const b = values.pop()!
+ const a = values.pop()!
+ values.push(applyOp(op, a, b))
+ }
+ }
+ ops.push(token)
+ } else {
+ // Will throw and error if there is an unknown token in the expression
+ throw new Error(`Unknown token: ${token}`)
+ }
+ }
+
+ // Apply remaining operators to remaining values
+ while (ops.length > 0) {
+ const op = ops.pop()!
+ if (op === "not") {
+ const val = values.pop()!
+ values.push(applyOp(op, val))
+ } else {
+ const b = values.pop()!
+ const a = values.pop()!
+ values.push(applyOp(op, a, b))
+ }
+ }
+
+ return values.pop()!
+}
diff --git a/packages/logical-expressions/src/index.ts b/packages/logical-expressions/src/index.ts
new file mode 100644
index 000000000..efa595ddf
--- /dev/null
+++ b/packages/logical-expressions/src/index.ts
@@ -0,0 +1,4 @@
+import tokenize from "./tokenize"
+import evaluate from "./evaluate"
+
+export { tokenize, evaluate }
diff --git a/packages/logical-expressions/src/tokenize.ts b/packages/logical-expressions/src/tokenize.ts
new file mode 100644
index 000000000..8825f2273
--- /dev/null
+++ b/packages/logical-expressions/src/tokenize.ts
@@ -0,0 +1,10 @@
+/**
+ * Tokenization function to split the expression into meaningful tokens.
+ * @param expression The expression to tokenize.
+ * @returns A list with the tokens of the expression.
+ */
+export default function tokenize(expression: string): string[] {
+ const tokenPattern = /\s*(\(|\)|and|or|not|xor|true|false)\s*/g
+ // Split the expression based on the token pattern and filter out empty tokens
+ return expression.split(tokenPattern).filter((token) => token.trim() !== "")
+}
diff --git a/packages/logical-expressions/tests/index.test.ts b/packages/logical-expressions/tests/index.test.ts
new file mode 100644
index 000000000..73e445f72
--- /dev/null
+++ b/packages/logical-expressions/tests/index.test.ts
@@ -0,0 +1,68 @@
+import tokenize from "../src/tokenize"
+import evaluate from "../src/evaluate"
+
+describe("Logical Expressions", () => {
+ describe("# tokenize", () => {
+ it("Should sucessfully tokenize a logical expression", () => {
+ const expression = "true and false or ( true and true )"
+
+ const tokens = tokenize(expression)
+
+ const result = ["true", "and", "false", "or", "(", "true", "and", "true", ")"]
+
+ expect(tokens).toStrictEqual(result)
+ })
+ })
+ describe("# evaluate", () => {
+ it("Should sucessfully evaluate a logical expression with the and operator", () => {
+ const expression = ["true", "and", "false"]
+ const result = evaluate(expression)
+ expect(result).toBeFalsy()
+ })
+ it("Should sucessfully evaluate a logical expression with the or operator", () => {
+ const expression = ["true", "or", "false"]
+ const result = evaluate(expression)
+ expect(result).toBeTruthy()
+ })
+ it("Should sucessfully evaluate a logical expression with the not operator", () => {
+ const expression = ["not", "false"]
+ const result = evaluate(expression)
+ expect(result).toBeTruthy()
+ })
+ it("Should sucessfully evaluate a logical expression with the xor operator", () => {
+ const expression = ["false", "xor", "true"]
+ const result = evaluate(expression)
+ expect(result).toBeTruthy()
+ })
+ it("Should sucessfully evaluate a logical expression with the and or not and xor operators", () => {
+ const expression = ["true", "and", "false", "or", "not", "false", "xor", "true"]
+ const result = evaluate(expression)
+ expect(result).toBeFalsy()
+ })
+ it("Should sucessfully evaluate a logical expression with parentheses", () => {
+ const expression = [
+ "true",
+ "and",
+ "false",
+ "or",
+ "(",
+ "true",
+ "and",
+ "true",
+ ")",
+ "or",
+ "(",
+ "not",
+ "false",
+ ")"
+ ]
+ const result = evaluate(expression)
+ expect(result).toBeTruthy()
+ })
+ it("Should throw an error because the operator is unknown", () => {
+ const expression = ["true", "op", "true"]
+ const fun = () => evaluate(expression)
+ expect(fun).toThrow("Unknown token: op")
+ })
+ })
+})
diff --git a/packages/logical-expressions/tsconfig.json b/packages/logical-expressions/tsconfig.json
new file mode 100644
index 000000000..71510a096
--- /dev/null
+++ b/packages/logical-expressions/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src", "tests", "rollup.config.ts"]
+}
diff --git a/packages/logical-expressions/typedoc.json b/packages/logical-expressions/typedoc.json
new file mode 100644
index 000000000..77a471c91
--- /dev/null
+++ b/packages/logical-expressions/typedoc.json
@@ -0,0 +1,3 @@
+{
+ "entryPoints": ["src/index.ts"]
+}
diff --git a/yarn.lock b/yarn.lock
index 6a8bb08be..71a047777 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3093,6 +3093,17 @@ __metadata:
languageName: unknown
linkType: soft
+"@zk-kit/logical-expressions@workspace:packages/logical-expressions":
+ version: 0.0.0-use.local
+ resolution: "@zk-kit/logical-expressions@workspace:packages/logical-expressions"
+ dependencies:
+ "@rollup/plugin-typescript": "npm:^11.1.6"
+ rimraf: "npm:^5.0.5"
+ rollup: "npm:^4.12.0"
+ rollup-plugin-cleanup: "npm:^3.2.1"
+ languageName: unknown
+ linkType: soft
+
"@zk-kit/poseidon-cipher@workspace:packages/poseidon-cipher":
version: 0.0.0-use.local
resolution: "@zk-kit/poseidon-cipher@workspace:packages/poseidon-cipher"
From cecc777813e4ebdb69449ddb5a6db2c24c5a65e5 Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Fri, 26 Jul 2024 12:43:15 +0200
Subject: [PATCH 02/11] refactor(logical-expressions): throw an error if the
logical expression is incorrect
The algorithm throws an error if the logical expression is incorrect, eliminating the need for
previous validation.
re #307
---
package.json | 2 +-
packages/logical-expressions/src/evaluate.ts | 47 ++++++++++++++-----
.../logical-expressions/tests/index.test.ts | 39 ++++++++++++++-
3 files changed, 73 insertions(+), 15 deletions(-)
diff --git a/package.json b/package.json
index a4dab89c0..415fb6e25 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"build": "turbo _build",
"_test": "jest --coverage",
"test": "turbo _test",
- "test:library": "jest packages/${0}",
+ "test:library": "jest --coverage packages/${0}",
"version:bump": "yarn workspace @zk-kit/${0} version ${1} && yarn remove:stable-version-field ${0} && NO_HOOK=1 git commit -am \"chore(${0}): v${1}\" && git tag ${0}-v${1}",
"version:publish": "yarn workspaces foreach -A --no-private npm publish --tolerate-republish --access public",
"version:release": "changelogithub",
diff --git a/packages/logical-expressions/src/evaluate.ts b/packages/logical-expressions/src/evaluate.ts
index 2d1a547f9..136f138a9 100644
--- a/packages/logical-expressions/src/evaluate.ts
+++ b/packages/logical-expressions/src/evaluate.ts
@@ -34,13 +34,14 @@ function applyOp(op: string, a: boolean, b?: boolean): boolean {
}
/**
- * Function to evaluate the tokenized expression.
+ * Function to evaluate a tokenized expression.
* This algorithm is an adaptation of the
* {@link https://en.wikipedia.org/wiki/Shunting_yard_algorithm | Shunting Yard}
* algorithm to evaluate expressions with logical operators.
+ * If the logical expression is incorrect, an error will be thrown automatically,
+ * eliminating the need for previous validation.
* @param tokens Tokens of the expression.
* The tokens can be boolean values, operators or parentheses.
- * They represent a valid expression.
* @returns The boolean value after evaluating the expression.
*/
export default function evaluate(tokens: string[]): boolean {
@@ -61,11 +62,17 @@ export default function evaluate(tokens: string[]): boolean {
while (ops.length > 0 && ops[ops.length - 1] !== "(") {
const op = ops.pop()!
if (op === "not") {
- const val = values.pop()!
+ const val = values.pop()
+ if (val === undefined) {
+ throw new Error(`The operator '${op}' requires one value`)
+ }
values.push(applyOp(op, val))
} else {
- const b = values.pop()!
- const a = values.pop()!
+ const b = values.pop()
+ const a = values.pop()
+ if (b === undefined || a === undefined) {
+ throw new Error(`The operator '${op}' requires two values`)
+ }
values.push(applyOp(op, a, b))
}
}
@@ -75,18 +82,24 @@ export default function evaluate(tokens: string[]): boolean {
while (ops.length > 0 && precedence(ops[ops.length - 1]) >= precedence(token)) {
const op = ops.pop()!
if (op === "not") {
- const val = values.pop()!
+ const val = values.pop()
+ if (val === undefined) {
+ throw new Error(`The operator '${op}' requires one value`)
+ }
values.push(applyOp(op, val))
} else {
- const b = values.pop()!
- const a = values.pop()!
+ const b = values.pop()
+ const a = values.pop()
+ if (b === undefined || a === undefined) {
+ throw new Error(`The operator '${op}' requires two values`)
+ }
values.push(applyOp(op, a, b))
}
}
ops.push(token)
} else {
// Will throw and error if there is an unknown token in the expression
- throw new Error(`Unknown token: ${token}`)
+ throw new Error(`Unknown token: '${token}'`)
}
}
@@ -94,14 +107,24 @@ export default function evaluate(tokens: string[]): boolean {
while (ops.length > 0) {
const op = ops.pop()!
if (op === "not") {
- const val = values.pop()!
+ const val = values.pop()
+ if (val === undefined) {
+ throw new Error(`The operator '${op}' requires one value`)
+ }
values.push(applyOp(op, val))
} else {
- const b = values.pop()!
- const a = values.pop()!
+ const b = values.pop()
+ const a = values.pop()
+ if (b === undefined || a === undefined) {
+ throw new Error(`The operator '${op}' requires two values`)
+ }
values.push(applyOp(op, a, b))
}
}
+ if (values.length > 1) {
+ throw new Error("Invalid logical expression")
+ }
+
return values.pop()!
}
diff --git a/packages/logical-expressions/tests/index.test.ts b/packages/logical-expressions/tests/index.test.ts
index 73e445f72..52c4d5595 100644
--- a/packages/logical-expressions/tests/index.test.ts
+++ b/packages/logical-expressions/tests/index.test.ts
@@ -59,10 +59,45 @@ describe("Logical Expressions", () => {
const result = evaluate(expression)
expect(result).toBeTruthy()
})
- it("Should throw an error because the operator is unknown", () => {
+ it("Should throw an error because there is an unknown token", () => {
const expression = ["true", "op", "true"]
const fun = () => evaluate(expression)
- expect(fun).toThrow("Unknown token: op")
+ expect(fun).toThrow("Unknown token: 'op'")
+ })
+ it("Should throw an error because there is an additional boolean value", () => {
+ const expression = ["true", "true", "and", "false"]
+ const fun = () => evaluate(expression)
+ expect(fun).toThrow("Invalid logical expression")
+ })
+ it("Should throw an error because it is an invalid logical expression with a unary operator", () => {
+ const expression = ["not"]
+ const fun = () => evaluate(expression)
+ expect(fun).toThrow("The operator 'not' requires one value")
+ })
+ it("Should throw an error because it is an invalid logical expression with a unary operator and parentheses", () => {
+ const expression = ["(", "not", ")"]
+ const fun = () => evaluate(expression)
+ expect(fun).toThrow("The operator 'not' requires one value")
+ })
+ it("Should throw an error because it is an invalid logical expression with a binary operator", () => {
+ const expression = ["true", "and"]
+ const fun = () => evaluate(expression)
+ expect(fun).toThrow("The operator 'and' requires two values")
+ })
+ it("Should throw an error because it is an invalid logical expression with a binary operator and parentheses", () => {
+ const expression = ["(", "true", "and", ")"]
+ const fun = () => evaluate(expression)
+ expect(fun).toThrow("The operator 'and' requires two values")
+ })
+ it("Should throw an error because it is an invalid logical expression with two unary operators with the same precedence", () => {
+ const expression = ["not", "not"]
+ const fun = () => evaluate(expression)
+ expect(fun).toThrow("The operator 'not' requires one value")
+ })
+ it("Should throw an error because it is an invalid logical expression with two binary operators with the same precedence", () => {
+ const expression = ["and", "or"]
+ const fun = () => evaluate(expression)
+ expect(fun).toThrow("The operator 'and' requires two value")
})
})
})
From 74779105165b75acad18d1a894880eaab30e943e Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Fri, 26 Jul 2024 13:10:37 +0200
Subject: [PATCH 03/11] refactor(logical-expressions): throw an error if the
logical expression is empty
re #307
---
packages/logical-expressions/src/evaluate.ts | 2 +-
.../logical-expressions/tests/index.test.ts | 21 ++++++++++++-------
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/packages/logical-expressions/src/evaluate.ts b/packages/logical-expressions/src/evaluate.ts
index 136f138a9..f86202e30 100644
--- a/packages/logical-expressions/src/evaluate.ts
+++ b/packages/logical-expressions/src/evaluate.ts
@@ -122,7 +122,7 @@ export default function evaluate(tokens: string[]): boolean {
}
}
- if (values.length > 1) {
+ if (values.length !== 1) {
throw new Error("Invalid logical expression")
}
diff --git a/packages/logical-expressions/tests/index.test.ts b/packages/logical-expressions/tests/index.test.ts
index 52c4d5595..ce1905586 100644
--- a/packages/logical-expressions/tests/index.test.ts
+++ b/packages/logical-expressions/tests/index.test.ts
@@ -59,45 +59,50 @@ describe("Logical Expressions", () => {
const result = evaluate(expression)
expect(result).toBeTruthy()
})
- it("Should throw an error because there is an unknown token", () => {
+ it("Should throw an error if the logical expression has an unknown token", () => {
const expression = ["true", "op", "true"]
const fun = () => evaluate(expression)
expect(fun).toThrow("Unknown token: 'op'")
})
- it("Should throw an error because there is an additional boolean value", () => {
+ it("Should throw an error if the logical expression has an additional boolean value", () => {
const expression = ["true", "true", "and", "false"]
const fun = () => evaluate(expression)
expect(fun).toThrow("Invalid logical expression")
})
- it("Should throw an error because it is an invalid logical expression with a unary operator", () => {
+ it("Should throw an error if the logical expression is invalid and has a unary operator", () => {
const expression = ["not"]
const fun = () => evaluate(expression)
expect(fun).toThrow("The operator 'not' requires one value")
})
- it("Should throw an error because it is an invalid logical expression with a unary operator and parentheses", () => {
+ it("Should throw an error if the logical expression is invalid and has a unary operator and parentheses", () => {
const expression = ["(", "not", ")"]
const fun = () => evaluate(expression)
expect(fun).toThrow("The operator 'not' requires one value")
})
- it("Should throw an error because it is an invalid logical expression with a binary operator", () => {
+ it("Should throw an error if the logical expression is invalid and has a binary operator", () => {
const expression = ["true", "and"]
const fun = () => evaluate(expression)
expect(fun).toThrow("The operator 'and' requires two values")
})
- it("Should throw an error because it is an invalid logical expression with a binary operator and parentheses", () => {
+ it("Should throw an error if the logical expression is invalid and has a binary operator and parentheses", () => {
const expression = ["(", "true", "and", ")"]
const fun = () => evaluate(expression)
expect(fun).toThrow("The operator 'and' requires two values")
})
- it("Should throw an error because it is an invalid logical expression with two unary operators with the same precedence", () => {
+ it("Should throw an error if the logical expression is invalid and has two unary operators with the same precedence", () => {
const expression = ["not", "not"]
const fun = () => evaluate(expression)
expect(fun).toThrow("The operator 'not' requires one value")
})
- it("Should throw an error because it is an invalid logical expression with two binary operators with the same precedence", () => {
+ it("Should throw an error if the logical expression is invalid and has two binary operators with the same precedence", () => {
const expression = ["and", "or"]
const fun = () => evaluate(expression)
expect(fun).toThrow("The operator 'and' requires two value")
})
+ it("Should throw an error if the logical expression is empty", () => {
+ const expression: string[] = []
+ const fun = () => evaluate(expression)
+ expect(fun).toThrow("Invalid logical expression")
+ })
})
})
From e92743cb9dca570a6aa2fd12fdb68840076f88a7 Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Fri, 26 Jul 2024 13:41:50 +0200
Subject: [PATCH 04/11] docs(logical-expressions): add instructions to install
and use the package
re #307
---
packages/logical-expressions/README.md | 61 ++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
diff --git a/packages/logical-expressions/README.md b/packages/logical-expressions/README.md
index d67bf9e22..b78638414 100644
--- a/packages/logical-expressions/README.md
+++ b/packages/logical-expressions/README.md
@@ -43,3 +43,64 @@
| This library facilitates the work with logical expressions. It allows you to tokenize and evaluate logical expressions. |
| ----------------------------------------------------------------------------------------------------------------------- |
+
+## 🛠 Install
+
+### npm or yarn
+
+Install the `@zk-kit/logical-expressions` package with npm:
+
+```bash
+npm i @zk-kit/logical-expressions
+```
+
+or yarn:
+
+```bash
+yarn add @zk-kit/logical-expressions
+```
+
+### CDN
+
+You can also load it using a `script` tag using [unpkg](https://unpkg.com/):
+
+```html
+
+```
+
+or [JSDelivr](https://www.jsdelivr.com/):
+
+```html
+
+```
+
+## 📜 Usage
+
+## Tokenize a logical expression
+
+\# **tokenize**(): _string[]_
+
+Tokenizes a logical (boolean) expression.
+Splits the expression into meaningful tokens.
+
+```ts
+import { tokenize } from "@zk-kit/logical-expressions"
+
+const expression = "true and false or ( true and true )"
+
+const tokens = tokenize(expression) // ["true", "and", "false", "or", "(", "true", "and", "true", ")"]
+```
+
+## Evaluate a tokenized logical expression
+
+\# **evaluate**(): _boolean_
+
+Evaluates a tokenized logical (boolean) expression. If the logical expression is incorrect, an error will be thrown automatically, eliminating the need for previous validation.
+
+```ts
+import { evaluate } from "@zk-kit/logical-expressions"
+
+const expression = ["true", "and", "false"]
+
+const result = evaluate(expression) // false
+```
From f7a49988bcd6bfdfb473f2ececb7e94e3636b287 Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Fri, 26 Jul 2024 13:48:26 +0200
Subject: [PATCH 05/11] docs: update repo readme and codeowners files
re #307
---
.github/CODEOWNERS | 1 +
README.md | 31 +++++++++++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 5e144a742..b85d21ca6 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -14,3 +14,4 @@
/packages/poseidon-proof/ @cedoor @vplasencia @0xjei
/packages/imt/ @cedoor
/packages/utils/ @cedoor @artwyman
+/packages/logical-expressions/ @vplasencia
diff --git a/README.md b/README.md
index 7ee9d3e7d..1bd15ad55 100644
--- a/README.md
+++ b/README.md
@@ -336,6 +336,37 @@
❌
+
+
+
+ @zk-kit/logical-expressions
+
+
+ (docs)
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+ ❌
+ |
+
From ecb4ffc4d357c52e4397f8520fedac95d7d47f8b Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Fri, 26 Jul 2024 14:40:22 +0200
Subject: [PATCH 06/11] docs(logical-expressions): improve code documentation
re #307
---
packages/logical-expressions/README.md | 18 ++++++++++++++----
packages/logical-expressions/src/evaluate.ts | 16 +++++++++++++++-
packages/logical-expressions/src/tokenize.ts | 10 ++++++++++
3 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/packages/logical-expressions/README.md b/packages/logical-expressions/README.md
index b78638414..d3d944445 100644
--- a/packages/logical-expressions/README.md
+++ b/packages/logical-expressions/README.md
@@ -41,8 +41,8 @@
-| This library facilitates the work with logical expressions. It allows you to tokenize and evaluate logical expressions. |
-| ----------------------------------------------------------------------------------------------------------------------- |
+| This library facilitates the work with logical (boolean) expressions. It allows you to tokenize and evaluate any logical expression. It supports the use of parentheses. |
+| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
## 🛠 Install
@@ -76,6 +76,10 @@ or [JSDelivr](https://www.jsdelivr.com/):
## 📜 Usage
+Logical operators supported: `and`, `or`, `not`, `xor`.
+
+All other existing logical operators (`nand`, `nor`, `xnor`) can be generated using the supported logical operators.
+
## Tokenize a logical expression
\# **tokenize**(): _string[]_
@@ -88,7 +92,10 @@ import { tokenize } from "@zk-kit/logical-expressions"
const expression = "true and false or ( true and true )"
-const tokens = tokenize(expression) // ["true", "and", "false", "or", "(", "true", "and", "true", ")"]
+const tokens = tokenize(expression)
+
+console.log(tokens)
+// Output: ["true", "and", "false", "or", "(", "true", "and", "true", ")"]
```
## Evaluate a tokenized logical expression
@@ -102,5 +109,8 @@ import { evaluate } from "@zk-kit/logical-expressions"
const expression = ["true", "and", "false"]
-const result = evaluate(expression) // false
+const result = evaluate(expression)
+
+console.log(result)
+// Output: false
```
diff --git a/packages/logical-expressions/src/evaluate.ts b/packages/logical-expressions/src/evaluate.ts
index f86202e30..9a26b4f92 100644
--- a/packages/logical-expressions/src/evaluate.ts
+++ b/packages/logical-expressions/src/evaluate.ts
@@ -40,9 +40,23 @@ function applyOp(op: string, a: boolean, b?: boolean): boolean {
* algorithm to evaluate expressions with logical operators.
* If the logical expression is incorrect, an error will be thrown automatically,
* eliminating the need for previous validation.
+ *
+ * Logical operators supported: `and`, `or`, `not`, `xor`.
+ * All other existing logical operators (`nand`, `nor`, `xnor`)
+ * can be generated using the supported logical operators.
* @param tokens Tokens of the expression.
- * The tokens can be boolean values, operators or parentheses.
+ * The tokens can be boolean values, logical operators or parentheses.
* @returns The boolean value after evaluating the expression.
+ *
+ * @example
+ * // Example usage:
+ * import { evaluate } from "@zk-kit/logical-expressions"
+ *
+ * const expression = ["true", "and", "false"]
+ * const result = evaluate(expression)
+ *
+ * console.log(result)
+ * // Output: false
*/
export default function evaluate(tokens: string[]): boolean {
const values: boolean[] = [] // Stack to store boolean values
diff --git a/packages/logical-expressions/src/tokenize.ts b/packages/logical-expressions/src/tokenize.ts
index 8825f2273..0ff0eda3a 100644
--- a/packages/logical-expressions/src/tokenize.ts
+++ b/packages/logical-expressions/src/tokenize.ts
@@ -2,6 +2,16 @@
* Tokenization function to split the expression into meaningful tokens.
* @param expression The expression to tokenize.
* @returns A list with the tokens of the expression.
+ *
+ * @example
+ * // Example usage:
+ * import { tokenize } from "@zk-kit/logical-expressions"
+ *
+ * const expression = "true and false or ( true and true )"
+ * const tokens = tokenize(expression)
+ *
+ * console.log(tokens)
+ * // Output: ["true", "and", "false", "or", "(", "true", "and", "true", ")"]
*/
export default function tokenize(expression: string): string[] {
const tokenPattern = /\s*(\(|\)|and|or|not|xor|true|false)\s*/g
From 767f508556a14e2d0a6d9362fe58844820484085 Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Fri, 26 Jul 2024 16:22:37 +0200
Subject: [PATCH 07/11] refactor(logical-expressions): export the applyOperator
function
re #307
---
packages/logical-expressions/README.md | 20 ++++
packages/logical-expressions/src/evaluate.ts | 58 +++++++---
packages/logical-expressions/src/index.ts | 4 +-
.../logical-expressions/tests/index.test.ts | 109 ++++++++++++++++--
4 files changed, 165 insertions(+), 26 deletions(-)
diff --git a/packages/logical-expressions/README.md b/packages/logical-expressions/README.md
index d3d944445..54c625f14 100644
--- a/packages/logical-expressions/README.md
+++ b/packages/logical-expressions/README.md
@@ -98,6 +98,26 @@ console.log(tokens)
// Output: ["true", "and", "false", "or", "(", "true", "and", "true", ")"]
```
+## Apply Operator
+
+\# **applyOperator**(): _boolean_
+
+Applies unary or binary operators to boolean values.
+
+Logical operators supported: `and`, `or`, `not`, `xor`.
+
+```ts
+import { applyOperator } from "@zk-kit/logical-expressions"
+
+// Unary operator
+const result1 = applyOperator("not", true)
+console.log(result1) // Output: false
+
+// Binary operator
+const result2 = applyOperator("and", true, false)
+console.log(result2) // Output: false
+```
+
## Evaluate a tokenized logical expression
\# **evaluate**(): _boolean_
diff --git a/packages/logical-expressions/src/evaluate.ts b/packages/logical-expressions/src/evaluate.ts
index 9a26b4f92..b95db11f8 100644
--- a/packages/logical-expressions/src/evaluate.ts
+++ b/packages/logical-expressions/src/evaluate.ts
@@ -14,22 +14,48 @@ function precedence(op: string): number {
}
/**
- * Function to apply the operator to one or two boolean values.
- * @param op Operator to apply.
+ * Function to apply unary or binary operators to boolean values.
+ * Logical operators supported: `and`, `or`, `not`, `xor`.
+ * @param operator Operator to apply.
* @param a First boolean value.
- * @param b Second boolean value.
+ * @param b Second boolean value (optional, only used for binary operators).
* @returns The boolean value after applying the operator.
+ *
+ * @example
+ * import { applyOperator } from "@zk-kit/logical-expressions"
+ *
+ * // Unary operator
+ * const result1 = applyOperator("not", true);
+ * console.log(result1); // Output: false
+ *
+ * // Binary operator
+ * const result2 = applyOperator("and", true, false);
+ * console.log(result2); // Output: false
*/
-function applyOp(op: string, a: boolean, b?: boolean): boolean {
- switch (op) {
+export function applyOperator(operator: string, a: boolean, b?: boolean): boolean {
+ switch (operator) {
case "and":
- return a && b!
+ if (b === undefined) {
+ throw new Error(`The operator '${operator}' requires two values`)
+ }
+ return a && b
case "or":
- return a || b!
+ if (b === undefined) {
+ throw new Error(`The operator '${operator}' requires two values`)
+ }
+ return a || b
case "not":
+ if (b !== undefined) {
+ throw new Error(`The operator '${operator}' requires only one value`)
+ }
return !a
- default: // Case XOR
- return a !== b! // XOR returns true if only one of the operands is true
+ case "xor":
+ if (b === undefined) {
+ throw new Error(`The operator '${operator}' requires two values`)
+ }
+ return a !== b // XOR returns true if only one of the operands is true
+ default:
+ throw new Error(`Unknown operator: '${operator}'`)
}
}
@@ -58,7 +84,7 @@ function applyOp(op: string, a: boolean, b?: boolean): boolean {
* console.log(result)
* // Output: false
*/
-export default function evaluate(tokens: string[]): boolean {
+export function evaluate(tokens: string[]): boolean {
const values: boolean[] = [] // Stack to store boolean values
const ops: string[] = [] // Stack to store operators
@@ -80,14 +106,14 @@ export default function evaluate(tokens: string[]): boolean {
if (val === undefined) {
throw new Error(`The operator '${op}' requires one value`)
}
- values.push(applyOp(op, val))
+ values.push(applyOperator(op, val))
} else {
const b = values.pop()
const a = values.pop()
if (b === undefined || a === undefined) {
throw new Error(`The operator '${op}' requires two values`)
}
- values.push(applyOp(op, a, b))
+ values.push(applyOperator(op, a, b))
}
}
ops.pop() // Remove '(' from stack
@@ -100,14 +126,14 @@ export default function evaluate(tokens: string[]): boolean {
if (val === undefined) {
throw new Error(`The operator '${op}' requires one value`)
}
- values.push(applyOp(op, val))
+ values.push(applyOperator(op, val))
} else {
const b = values.pop()
const a = values.pop()
if (b === undefined || a === undefined) {
throw new Error(`The operator '${op}' requires two values`)
}
- values.push(applyOp(op, a, b))
+ values.push(applyOperator(op, a, b))
}
}
ops.push(token)
@@ -125,14 +151,14 @@ export default function evaluate(tokens: string[]): boolean {
if (val === undefined) {
throw new Error(`The operator '${op}' requires one value`)
}
- values.push(applyOp(op, val))
+ values.push(applyOperator(op, val))
} else {
const b = values.pop()
const a = values.pop()
if (b === undefined || a === undefined) {
throw new Error(`The operator '${op}' requires two values`)
}
- values.push(applyOp(op, a, b))
+ values.push(applyOperator(op, a, b))
}
}
diff --git a/packages/logical-expressions/src/index.ts b/packages/logical-expressions/src/index.ts
index efa595ddf..c1cd20510 100644
--- a/packages/logical-expressions/src/index.ts
+++ b/packages/logical-expressions/src/index.ts
@@ -1,4 +1,4 @@
import tokenize from "./tokenize"
-import evaluate from "./evaluate"
+import { applyOperator, evaluate } from "./evaluate"
-export { tokenize, evaluate }
+export { tokenize, applyOperator, evaluate }
diff --git a/packages/logical-expressions/tests/index.test.ts b/packages/logical-expressions/tests/index.test.ts
index ce1905586..d6f510081 100644
--- a/packages/logical-expressions/tests/index.test.ts
+++ b/packages/logical-expressions/tests/index.test.ts
@@ -1,9 +1,9 @@
import tokenize from "../src/tokenize"
-import evaluate from "../src/evaluate"
+import { applyOperator, evaluate } from "../src/evaluate"
describe("Logical Expressions", () => {
describe("# tokenize", () => {
- it("Should sucessfully tokenize a logical expression", () => {
+ it("Should successfully tokenize a logical expression", () => {
const expression = "true and false or ( true and true )"
const tokens = tokenize(expression)
@@ -13,33 +13,126 @@ describe("Logical Expressions", () => {
expect(tokens).toStrictEqual(result)
})
})
+ describe("# applyOperator", () => {
+ it("Should successfully apply the operator and", () => {
+ const operator = "and"
+
+ const a = true
+
+ const b = false
+
+ const result = applyOperator(operator, a, b)
+
+ expect(result).toBeFalsy()
+ })
+ it("Should successfully apply the operator or", () => {
+ const operator = "or"
+
+ const a = true
+
+ const b = false
+
+ const result = applyOperator(operator, a, b)
+
+ expect(result).toBeTruthy()
+ })
+ it("Should successfully apply the operator not", () => {
+ const operator = "not"
+
+ const a = true
+
+ const result = applyOperator(operator, a)
+
+ expect(result).toBeFalsy()
+ })
+ it("Should successfully apply the operator xor", () => {
+ const operator = "xor"
+
+ const a = true
+
+ const b = true
+
+ const result = applyOperator(operator, a, b)
+
+ expect(result).toBeFalsy()
+ })
+ it("Should throw an error if the operator is not supported", () => {
+ const operator = "op"
+
+ const a = true
+
+ const b = false
+
+ const fun = () => applyOperator(operator, a, b)
+
+ expect(fun).toThrow("Unknown operator: 'op'")
+ })
+ it("Should throw an error there is one missing argument when using the and operator", () => {
+ const operator = "and"
+
+ const a = true
+
+ const fun = () => applyOperator(operator, a)
+
+ expect(fun).toThrow("The operator 'and' requires two values")
+ })
+ it("Should throw an error there is one missing argument when using the or operator", () => {
+ const operator = "or"
+
+ const a = true
+
+ const fun = () => applyOperator(operator, a)
+
+ expect(fun).toThrow("The operator 'or' requires two values")
+ })
+ it("Should throw an error there is one missing argument when using the xor operator", () => {
+ const operator = "xor"
+
+ const a = true
+
+ const fun = () => applyOperator(operator, a)
+
+ expect(fun).toThrow("The operator 'xor' requires two values")
+ })
+ it("Should throw an error there is one additional argument when using the not operator", () => {
+ const operator = "not"
+
+ const a = true
+
+ const b = true
+
+ const fun = () => applyOperator(operator, a, b)
+
+ expect(fun).toThrow("The operator 'not' requires only one value")
+ })
+ })
describe("# evaluate", () => {
- it("Should sucessfully evaluate a logical expression with the and operator", () => {
+ it("Should successfully evaluate a logical expression with the and operator", () => {
const expression = ["true", "and", "false"]
const result = evaluate(expression)
expect(result).toBeFalsy()
})
- it("Should sucessfully evaluate a logical expression with the or operator", () => {
+ it("Should successfully evaluate a logical expression with the or operator", () => {
const expression = ["true", "or", "false"]
const result = evaluate(expression)
expect(result).toBeTruthy()
})
- it("Should sucessfully evaluate a logical expression with the not operator", () => {
+ it("Should successfully evaluate a logical expression with the not operator", () => {
const expression = ["not", "false"]
const result = evaluate(expression)
expect(result).toBeTruthy()
})
- it("Should sucessfully evaluate a logical expression with the xor operator", () => {
+ it("Should successfully evaluate a logical expression with the xor operator", () => {
const expression = ["false", "xor", "true"]
const result = evaluate(expression)
expect(result).toBeTruthy()
})
- it("Should sucessfully evaluate a logical expression with the and or not and xor operators", () => {
+ it("Should successfully evaluate a logical expression with the and or not and xor operators", () => {
const expression = ["true", "and", "false", "or", "not", "false", "xor", "true"]
const result = evaluate(expression)
expect(result).toBeFalsy()
})
- it("Should sucessfully evaluate a logical expression with parentheses", () => {
+ it("Should successfully evaluate a logical expression with parentheses", () => {
const expression = [
"true",
"and",
From 5f9bea4e5e69f0b9c2bcea8faf798b6d05e6c5e6 Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Fri, 26 Jul 2024 17:12:49 +0200
Subject: [PATCH 08/11] refactor(logical-expressions): export the precedence
function
re #307
---
packages/logical-expressions/README.md | 25 ++++++++++++----
packages/logical-expressions/src/evaluate.ts | 21 +++++++++-----
packages/logical-expressions/src/index.ts | 4 +--
.../logical-expressions/tests/index.test.ts | 29 ++++++++++++++++++-
4 files changed, 64 insertions(+), 15 deletions(-)
diff --git a/packages/logical-expressions/README.md b/packages/logical-expressions/README.md
index 54c625f14..a42c60ebb 100644
--- a/packages/logical-expressions/README.md
+++ b/packages/logical-expressions/README.md
@@ -76,7 +76,7 @@ or [JSDelivr](https://www.jsdelivr.com/):
## 📜 Usage
-Logical operators supported: `and`, `or`, `not`, `xor`.
+Supported logical operators: `and`, `or`, `not`, `xor`.
All other existing logical operators (`nand`, `nor`, `xnor`) can be generated using the supported logical operators.
@@ -98,24 +98,39 @@ console.log(tokens)
// Output: ["true", "and", "false", "or", "(", "true", "and", "true", ")"]
```
+## Precedence of a logical operator
+
+\# **precedence**(): _number_
+
+Returns the precedence of a logical operator. If the operator is not supported, the precedence will be 0.
+
+```ts
+import { precedence } from "@zk-kit/logical-expressions"
+
+const result = precedence("and")
+
+console.log(result)
+// Output: 1
+```
+
## Apply Operator
\# **applyOperator**(): _boolean_
Applies unary or binary operators to boolean values.
-Logical operators supported: `and`, `or`, `not`, `xor`.
-
```ts
import { applyOperator } from "@zk-kit/logical-expressions"
// Unary operator
const result1 = applyOperator("not", true)
-console.log(result1) // Output: false
+console.log(result1)
+// Output: false
// Binary operator
const result2 = applyOperator("and", true, false)
-console.log(result2) // Output: false
+console.log(result2)
+// Output: false
```
## Evaluate a tokenized logical expression
diff --git a/packages/logical-expressions/src/evaluate.ts b/packages/logical-expressions/src/evaluate.ts
index b95db11f8..3aad70cd5 100644
--- a/packages/logical-expressions/src/evaluate.ts
+++ b/packages/logical-expressions/src/evaluate.ts
@@ -2,14 +2,21 @@
* Function to determine the precedence of operators.
* Unary operators have higher precedence.
* Binary operators have the same precedence.
- * @param op Operator to check the precedence.
- * @returns The precedence of the operator.
+ * @param operator Operator to check the precedence.
+ * @returns The precedence of the operator or 0 if
+ * the operator is not supported
+ *
+ * @example
+ * import { precedence } from "@zk-kit/logical-expressions"
+ *
+ * const result = precedence("and");
+ * console.log(result); // Output: 1
*/
-function precedence(op: string): number {
- if (op === "not") return 2 // Highest precedence
- if (op === "and") return 1
- if (op === "xor") return 1
- if (op === "or") return 1 // Lowest precedence
+export function precedence(operator: string): number {
+ if (operator === "not") return 2 // Highest precedence
+ if (operator === "and") return 1
+ if (operator === "xor") return 1
+ if (operator === "or") return 1 // Lowest precedence
return 0
}
diff --git a/packages/logical-expressions/src/index.ts b/packages/logical-expressions/src/index.ts
index c1cd20510..2628601c3 100644
--- a/packages/logical-expressions/src/index.ts
+++ b/packages/logical-expressions/src/index.ts
@@ -1,4 +1,4 @@
import tokenize from "./tokenize"
-import { applyOperator, evaluate } from "./evaluate"
+import { precedence, applyOperator, evaluate } from "./evaluate"
-export { tokenize, applyOperator, evaluate }
+export { tokenize, precedence, applyOperator, evaluate }
diff --git a/packages/logical-expressions/tests/index.test.ts b/packages/logical-expressions/tests/index.test.ts
index d6f510081..aa0c1acfe 100644
--- a/packages/logical-expressions/tests/index.test.ts
+++ b/packages/logical-expressions/tests/index.test.ts
@@ -1,5 +1,5 @@
import tokenize from "../src/tokenize"
-import { applyOperator, evaluate } from "../src/evaluate"
+import { precedence, applyOperator, evaluate } from "../src/evaluate"
describe("Logical Expressions", () => {
describe("# tokenize", () => {
@@ -13,6 +13,33 @@ describe("Logical Expressions", () => {
expect(tokens).toStrictEqual(result)
})
})
+ describe("# precedence", () => {
+ it("Should successfully return the precedence of the and operator", () => {
+ const result = precedence("and")
+
+ expect(result).toBe(1)
+ })
+ it("Should successfully return the precedence of the or operator", () => {
+ const result = precedence("or")
+
+ expect(result).toBe(1)
+ })
+ it("Should successfully return the precedence of the xor operator", () => {
+ const result = precedence("xor")
+
+ expect(result).toBe(1)
+ })
+ it("Should successfully return the precedence of the not operator", () => {
+ const result = precedence("not")
+
+ expect(result).toBe(2)
+ })
+ it("Should return 0 if the operator is not supported", () => {
+ const result = precedence("op")
+
+ expect(result).toBe(0)
+ })
+ })
describe("# applyOperator", () => {
it("Should successfully apply the operator and", () => {
const operator = "and"
From 5c257d4c0c39f93bc79fbedebb3e256d35f23317 Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Fri, 26 Jul 2024 18:08:49 +0200
Subject: [PATCH 09/11] docs(logical-expressions): update evaluate function
docs
re #307
---
packages/logical-expressions/README.md | 8 +++++++-
packages/logical-expressions/src/evaluate.ts | 10 ++++++++--
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/packages/logical-expressions/README.md b/packages/logical-expressions/README.md
index a42c60ebb..dca7e3078 100644
--- a/packages/logical-expressions/README.md
+++ b/packages/logical-expressions/README.md
@@ -137,7 +137,13 @@ console.log(result2)
\# **evaluate**(): _boolean_
-Evaluates a tokenized logical (boolean) expression. If the logical expression is incorrect, an error will be thrown automatically, eliminating the need for previous validation.
+Evaluates a tokenized logical (boolean) expression.
+
+There is no need to verify the correctness of the logical expression before calling the evaluate function, as this will be checked during the evaluation. If the expression is incorrect, an error will be thrown automatically.
+
+Example of correct logical expression: `"true and false"`.
+
+Example of incorrect logical expression: `"true true and false"`.
```ts
import { evaluate } from "@zk-kit/logical-expressions"
diff --git a/packages/logical-expressions/src/evaluate.ts b/packages/logical-expressions/src/evaluate.ts
index 3aad70cd5..6c944407d 100644
--- a/packages/logical-expressions/src/evaluate.ts
+++ b/packages/logical-expressions/src/evaluate.ts
@@ -71,8 +71,14 @@ export function applyOperator(operator: string, a: boolean, b?: boolean): boolea
* This algorithm is an adaptation of the
* {@link https://en.wikipedia.org/wiki/Shunting_yard_algorithm | Shunting Yard}
* algorithm to evaluate expressions with logical operators.
- * If the logical expression is incorrect, an error will be thrown automatically,
- * eliminating the need for previous validation.
+ *
+ * There is no need to verify the correctness of the logical expression
+ * before calling the evaluate function, as this will be checked during
+ * the evaluation. If the expression is incorrect, an error will be thrown automatically.
+ *
+ * Example of correct logical expression: `"true and false"`.
+ *
+ * Example of incorrect logical expression: `"true true and false"`.
*
* Logical operators supported: `and`, `or`, `not`, `xor`.
* All other existing logical operators (`nand`, `nor`, `xnor`)
From 24d5f6ae01cd31f2514a69582e5864a50fb63ded Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Sat, 27 Jul 2024 12:28:44 +0200
Subject: [PATCH 10/11] docs(logical-expressions): update code docs
re #307
---
packages/logical-expressions/README.md | 12 ++++--------
packages/logical-expressions/src/evaluate.ts | 19 +++++++++++--------
packages/logical-expressions/src/tokenize.ts | 1 +
3 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/packages/logical-expressions/README.md b/packages/logical-expressions/README.md
index dca7e3078..6192db786 100644
--- a/packages/logical-expressions/README.md
+++ b/packages/logical-expressions/README.md
@@ -109,8 +109,7 @@ import { precedence } from "@zk-kit/logical-expressions"
const result = precedence("and")
-console.log(result)
-// Output: 1
+console.log(result) // Output: 1
```
## Apply Operator
@@ -124,13 +123,11 @@ import { applyOperator } from "@zk-kit/logical-expressions"
// Unary operator
const result1 = applyOperator("not", true)
-console.log(result1)
-// Output: false
+console.log(result1) // Output: false
// Binary operator
const result2 = applyOperator("and", true, false)
-console.log(result2)
-// Output: false
+console.log(result2) // Output: false
```
## Evaluate a tokenized logical expression
@@ -152,6 +149,5 @@ const expression = ["true", "and", "false"]
const result = evaluate(expression)
-console.log(result)
-// Output: false
+console.log(result) // Output: false
```
diff --git a/packages/logical-expressions/src/evaluate.ts b/packages/logical-expressions/src/evaluate.ts
index 6c944407d..5cebd4263 100644
--- a/packages/logical-expressions/src/evaluate.ts
+++ b/packages/logical-expressions/src/evaluate.ts
@@ -7,10 +7,12 @@
* the operator is not supported
*
* @example
+ * // Example usage:
* import { precedence } from "@zk-kit/logical-expressions"
*
- * const result = precedence("and");
- * console.log(result); // Output: 1
+ * const result = precedence("and")
+ *
+ * console.log(result) // Output: 1
*/
export function precedence(operator: string): number {
if (operator === "not") return 2 // Highest precedence
@@ -29,15 +31,16 @@ export function precedence(operator: string): number {
* @returns The boolean value after applying the operator.
*
* @example
+ * // Example usage:
* import { applyOperator } from "@zk-kit/logical-expressions"
*
* // Unary operator
- * const result1 = applyOperator("not", true);
- * console.log(result1); // Output: false
+ * const result1 = applyOperator("not", true)
+ * console.log(result1) // Output: false
*
* // Binary operator
- * const result2 = applyOperator("and", true, false);
- * console.log(result2); // Output: false
+ * const result2 = applyOperator("and", true, false)
+ * console.log(result2) // Output: false
*/
export function applyOperator(operator: string, a: boolean, b?: boolean): boolean {
switch (operator) {
@@ -92,10 +95,10 @@ export function applyOperator(operator: string, a: boolean, b?: boolean): boolea
* import { evaluate } from "@zk-kit/logical-expressions"
*
* const expression = ["true", "and", "false"]
+ *
* const result = evaluate(expression)
*
- * console.log(result)
- * // Output: false
+ * console.log(result) // Output: false
*/
export function evaluate(tokens: string[]): boolean {
const values: boolean[] = [] // Stack to store boolean values
diff --git a/packages/logical-expressions/src/tokenize.ts b/packages/logical-expressions/src/tokenize.ts
index 0ff0eda3a..03dbade70 100644
--- a/packages/logical-expressions/src/tokenize.ts
+++ b/packages/logical-expressions/src/tokenize.ts
@@ -8,6 +8,7 @@
* import { tokenize } from "@zk-kit/logical-expressions"
*
* const expression = "true and false or ( true and true )"
+ *
* const tokens = tokenize(expression)
*
* console.log(tokens)
From f673c1094d0d5995757e53bd93eabb9c847a334c Mon Sep 17 00:00:00 2001
From: Vivian Plasencia
Date: Wed, 31 Jul 2024 14:32:04 +0200
Subject: [PATCH 11/11] chore(logical-expressions): update package json file
---
packages/logical-expressions/package.json | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/packages/logical-expressions/package.json b/packages/logical-expressions/package.json
index 8b616adbf..c2349a419 100644
--- a/packages/logical-expressions/package.json
+++ b/packages/logical-expressions/package.json
@@ -15,15 +15,18 @@
},
"files": [
"dist/",
- "src/",
- "LICENSE",
- "README.md"
+ "src/"
],
"repository": "git@github.com:privacy-scaling-explorations/zk-kit.git",
"homepage": "https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/logical-expressions",
"bugs": {
"url": "https://github.com/privacy-scaling-explorations/zk-kit.git/issues"
},
+ "author": {
+ "name": "Vivian Plasencia",
+ "email": "v.pcalana@gmail.com",
+ "url": "https://github.com/vplasencia"
+ },
"scripts": {
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
"prepublishOnly": "yarn build"