npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@spaceavocado/illogical

v2.1.0

Published

A micro conditional javascript engine used to parse the raw logical and comparison expressions, evaluate the expression in the given data context, and provide access to a text form of the given expressions.

Downloads

11

Readme

illogical

A micro conditional javascript engine used to parse the raw logical and comparison expressions, evaluate the expression in the given data context, and provide access to a text form of the given expressions.

Revision: Aug 20, 2024.

Other implementations:

About

This project has been developed to provide a shared conditional logic between front-end and back-end code, stored in JSON or in any other data serialization format.

Code documentation could be found here: https://spaceavocado.github.io/illogical/index.html.

The library is being build as CommonJS module and ESM.

Installation via NPM or Yarn

npm install -D @spaceavocado/illogical
yarn add @spaceavocado/illogical -D

Table of Content



Basic Usage

// Import the illogical engine
import illogical from '@spaceavocado/illogical'

// Create a new instance of the engine
const i = illogical()

// Evaluate the raw expression
const result = i.evaluate(['==', 5, 5])

For advanced usage, please Engine Options.

Evaluate

Evaluate comparison or logical expression:

i.evaluate(Comparison Expression or Logical Expression, Evaluation Data Context) => boolean

Data context is optional.

Example

// Comparison expression
i.evaluate(['==', 5, 5])
i.evaluate(['==', 'circle', 'circle'])
i.evaluate(['==', true, true])
i.evaluate(['==', '$name', 'peter'], { name: 'peter' })
i.evaluate(['UNDEFINED', '$RefA'], {})

// Logical expression
i.evaluate(['AND', ['==', 5, 5], ['==', 10, 10]])
i.evaluate(['AND', ['==', 'circle', 'circle'], ['==', 10, 10]])
i.evaluate(['OR', ['==', '$name', 'peter'], ['==', 5, 10]], { name: 'peter' })

Statement

Get expression string representation:

i.statement(Comparison Expression or Logical Expression) => string

Example

/* Comparison expression */

i.statement(['==', 5, 5]) // (5 == 5)
i.statement(['==', 'circle', 'circle']) // ("circle" == "circle")
i.statement(['==', true, true]) // (true == true)
i.statement(['==', '$name', 'peter'], { name: 'peter' }) // ({name} == "peter")
i.statement(['UNDEFINED', '$RefA']) // ({RefA} is UNDEFINED)

/* Logical expression */

i.statement(['AND', ['==', 5, 5], ['==', 10, 10]]) // ((5 == 5) AND (10 == 10))
i.statement(['AND', ['==', 'circle', 'circle'], ['==', 10, 10]]) // (("circle" == "circle") AND (10 == 10))
i.statement(['OR', ['==', '$name', 'peter'], ['==', 5, 10]], { name: 'peter' }) // (({name} == "peter") OR (5 == 10))

Parse

Parse the expression into a evaluable object, i.e. it returns the parsed self-evaluable condition expression.

i.parse(Comparison Expression or Logical Expression) => evaluable

Evaluate Function

Example

let evaluable = i.parse(['==', '$name', 'peter'])

evaluable.evaluate({ name: 'peter' }) // true
evaluable.toString() // ({name} == "peter")

Simplify

Simplifies an expression with a given context. This is useful when you already have some of the properties of context and wants to try to evaluate the expression.

Example

i.simplify(['AND', ['==', '$a', 10], ['==', '$b', 20]], { a: 10 }) // ['==', '$b', 20]
i.simplify(['AND', ['==', '$a', 10], ['==', '$b', 20]], { a: 20 }) // false

Values not found in the context will cause the parent operand not to be evaluated and returned as part of the simplified expression.

In some situations we might want to evaluate the expression even if referred value is not present. You can provide a list of keys that will be strictly evaluated even if they are not present in the context.

Example

i.simplify(
  ['AND', ['==', '$a', 10], ['==', '$b', 20]],
  { a: 10 },
  ['b'] // '$b' will be evaluated to undefined.
) // false

Alternatively we might want to do the opposite and strictly evaluate the expression for all referred values not present in the context except for a specified list of optional keys.

Example

i.simplify(
  ['OR', ['==', '$a', 10], ['==', '$b', 20], ['==', '$c', 20]],
  { c: 10 },
  undefined,
  ['b'] // except for '$b' everything not in context will be evaluated to undefined.
) // ['==', '$b', 20]

Working with Expressions

Evaluation Data Context

The evaluation data context is used to provide the expression with variable references, i.e. this allows for the dynamic expressions. The data context is object with properties used as the references keys, and its values as reference values.

Valid reference values: object, string, number, boolean, string[], number[].

To reference the nested reference, please use "." delimiter, e.g.: $address.city

Accessing Array Element:

$options[1]

Accessing Array Element via Reference:

$options[{index}]

  • The index reference is resolved within the data context as an array index.

Nested Referencing

$address.{segment}

  • The segment reference is resolved within the data context as a property key.

Composite Reference Key

$shape{shapeType}

  • The shapeType reference is resolved within the data context, and inserted into the outer reference key.
  • E.g. shapeType is resolved as "B" and would compose the $shapeB outer reference.
  • This resolution could be n-nested.

Data Type Casting

$payment.amount.(Type)

Cast the given data context into the desired data type before being used as an operand in the evaluation.

Note: If the conversion is invalid, then a warning message is being logged.

Supported data type conversions:

  • .(String): cast a given reference to String.
  • .(Number): cast a given reference to Number.
  • .(Integer): cast a given reference to whole number.
  • .(Float): cast a given reference to floating point number.
  • .(Boolean): cast a given reference to boolean value.

Example

// Data context
const ctx = {
  name: 'peter',
  country: 'canada',
  age: 21,
  options: [1, 2, 3],
  address: {
    city: 'Toronto',
    country: 'Canada',
  },
  index: 2,
  segment: 'city',
  shapeA: 'box',
  shapeB: 'circle',
  shapeType: 'B',
}

// Evaluate an expression in the given data context
i.evaluate(['>', '$age', 20], ctx) // true

// Evaluate an expression in the given data context
i.evaluate(['==', '$address.city', 'Toronto'], ctx) // true

// Accessing Array Element
i.evaluate(['==', '$options[1]', 2], ctx) // true

// Accessing Array Element via Reference
i.evaluate(['==', '$options[{index}]', 3], ctx) // true

// Nested Referencing
i.evaluate(['==', '$address.{segment}', 'Toronto'], ctx) // true

// Composite Reference Key
i.evaluate(['==', '$shape{shapeType}', 'circle'], ctx) // true

// Data Type Casting
i.evaluate(['==', '$age.(String)', '21'], ctx) // true

Operand Types

The Comparison Expression expect operands to be one of the below:

Value

Simple value types: string, number, boolean.

Example

;['==', 5, 5][('==', 'circle', 'circle')][('==', true, true)]

Reference

The reference operand value is resolved from the Evaluation Data Context, where the the operands name is used as key in the context.

The reference operand must be prefixed with $ symbol, e.g.: $name. This might be customized via Reference Predicate Parser Option.

Example

| Expression | Data Context | | ----------------------------- | ----------------- | | ['==', '$age', 21] | {age: 21} | | ['==', 'circle', '$shape'] | {age: 'circle'} | | ['==', '$visible', true] | {visible: true} |

Collection

The operand could be an array mixed from Value and Reference.

Example

| Expression | Data Context | | ---------------------------------------- | ----------------------------------- | | ['IN', [1, 2], 1] | {} | | ['IN', 'circle', ['$shapeA', $shapeB] | {shapeA: 'circle', shapeB: 'box'} | | ['IN', [$number, 5], 5] | {number: 3} |

Comparison Expressions

Equal

Expression format: ["==", Left Operand, Right Operand].

Valid operand types: string, number, boolean.

["==", 5, 5]
i.evaluate(['==', 5, 5]) // true

Not Equal

Expression format: ["!=", Left Operand, Right Operand].

Valid operand types: string, number, boolean.

["!=", "circle", "square"]
i.evaluate(['!=', 'circle', 'square']) // true

Greater Than

Expression format: [">", Left Operand, Right Operand].

Valid operand types: number.

[">", 10, 5]
i.evaluate(['>', 10, 5]) // true

Greater Than or Equal

Expression format: [">=", Left Operand, Right Operand].

Valid operand types: number.

[">=", 5, 5]
i.evaluate(['>=', 5, 5]) // true

Less Than

Expression format: ["<", Left Operand, Right Operand].

Valid operand types: number.

["<", 5, 10]
i.evaluate(['<', 5, 10]) // true

Less Than or Equal

Expression format: ["<=", Left Operand, Right Operand].

Valid operand types: number.

["<=", 5, 5]
i.evaluate(['<=', 5, 5]) // true

In

Expression format: ["IN", Left Operand, Right Operand].

Valid operand types: number and number[] or string and string[].

["IN", 5, [1,2,3,4,5]]
["IN", ["circle", "square", "triangle"], "square"]
i.evaluate(['IN', 5, [1, 2, 3, 4, 5]]) // true
i.evaluate(['IN', ['circle', 'square', 'triangle'], 'square']) // true

Not In

Expression format: ["NOT IN", Left Operand, Right Operand].

Valid operand types: number and number[] or string and string[].

["IN", 10, [1,2,3,4,5]]
["IN", ["circle", "square", "triangle"], "oval"]
i.evaluate(['NOT IN', 10, [1, 2, 3, 4, 5]]) // true
i.evaluate(['NOT IN', ['circle', 'square', 'triangle'], 'oval']) // true

Prefix

Expression format: ["PREFIX", Left Operand, Right Operand].

Valid operand types: string.

  • Left operand is the PREFIX term.
  • Right operand is the tested word.
["PREFIX", "hemi", "hemisphere"]
i.evaluate(['PREFIX', 'hemi', 'hemisphere']) // true
i.evaluate(['PREFIX', 'hemi', 'sphere']) // false

Suffix

Expression format: ["SUFFIX", Left Operand, Right Operand].

Valid operand types: string.

  • Left operand is the tested word.
  • Right operand is the SUFFIX term.
["SUFFIX", "establishment", "ment"]
i.evaluate(['SUFFIX', 'establishment', 'ment']) // true
i.evaluate(['SUFFIX', 'establish', 'ment']) // false

Overlap

Expression format: ["OVERLAP", Left Operand, Right Operand].

Valid operand types number[] or string[].

["OVERLAP", [1, 2], [1, 2, 3, 4, 5]]
["OVERLAP", ["circle", "square", "triangle"], ["square"]]
i.evaluate(['OVERLAP', [1, 2, 6], [1, 2, 3, 4, 5]]) // true
i.evaluate(['OVERLAP', ['circle', 'square', 'triangle'], ['square', 'oval']]) // true

Undefined

Expression format: ["UNDEFINED", Reference Operand].

["UNDEFINED", "$RefA"]
i.evaluate(['UNDEFINED', 'RefA'], {}) // true
i.evaluate(['UNDEFINED', 'RefA'], { RefA: undefined }) // true
i.evaluate(['UNDEFINED', 'RefA'], { RefA: 10 }) // false

Present

Evaluates as FALSE when the operand is UNDEFINED or NULL.

Expression format: ["PRESENT", Reference Operand].

["PRESENT", "$RefA"]
i.evaluate(['PRESENT', 'RefA'], {}) // false
i.evaluate(['PRESENT', 'RefA'], { RefA: undefined }) // false
i.evaluate(['PRESENT', 'RefA'], { RefA: null }) // false
i.evaluate(['PRESENT', 'RefA'], { RefA: 10 }) // true
i.evaluate(['PRESENT', 'RefA'], { RefA: false }) // true
i.evaluate(['PRESENT', 'RefA'], { RefA: 0 }) // true

Logical Expressions

And

The logical AND operator (&&) returns the boolean value TRUE if both operands are TRUE and returns FALSE otherwise.

Expression format: ["AND", Left Operand 1, Right Operand 2, ... , Right Operand N].

Valid operand types: Comparison Expression or Nested Logical Expression.

["AND", ["==", 5, 5], ["==", 10, 10]]
i.evaluate(['AND', ['==', 5, 5], ['==', 10, 10]]) // true

Or

The logical OR operator (||) returns the boolean value TRUE if either or both operands is TRUE and returns FALSE otherwise.

Expression format: ["OR", Left Operand 1, Right Operand 2, ... , Right Operand N].

Valid operand types: Comparison Expression or Nested Logical Expression.

["OR", ["==", 5, 5], ["==", 10, 5]]
i.evaluate(['OR', ['==', 5, 5], ['==', 10, 5]]) // true

Nor

The logical NOR operator returns the boolean value TRUE if both operands are FALSE and returns FALSE otherwise.

Expression format: ["NOR", Left Operand 1, Right Operand 2, ... , Right Operand N]

Valid operand types: Comparison Expression or Nested Logical Expression.

["NOR", ["==", 5, 1], ["==", 10, 5]]
i.evaluate(['NOR', ['==', 5, 1], ['==', 10, 5]]) // true

Xor

The logical NOR operator returns the boolean value TRUE if both operands are FALSE and returns FALSE otherwise.

Expression format: ["XOR", Left Operand 1, Right Operand 2, ... , Right Operand N]

Valid operand types: Comparison Expression or Nested Logical Expression.

["XOR", ["==", 5, 5], ["==", 10, 5]]
i.evaluate(['XOR', ['==', 5, 5], ['==', 10, 5]]) // true
["XOR", ["==", 5, 5], ["==", 10, 10]]
i.evaluate(['XOR', ['==', 5, 5], ['==', 10, 10]]) // false

Not

The logical NOT operator returns the boolean value TRUE if the operand is FALSE, TRUE otherwise.

Expression format: ["NOT", Operand]

Valid operand types: Comparison Expression or Nested Logical Expression.

["NOT", ["==", 5, 5]]
i.evaluate(['NOT', ['==', 5, 5]]) // true

Engine Options

Parser Options

Below described, are individual options object properties which could be used individually. Any missing options will be substituted with the default options.

Usage

// Import the illogical engine
import Engine from '@spaceavocado/illogical'

// Create a new instance of the engine
const opts = {
  referencePredicate: (operand) => operand.startsWith('$'),
}
const engine = new Engine(opts)

Reference Predicate

A function used to determine if the operand is a reference type, otherwise evaluated as a static value.

referencePredicate: (operand: string) => boolean

Return value:

  • true = reference type
  • false = value type

Default reference predicate:

The $ symbol at the begging of the operand is used to predicate the reference type., E.g. $State, $Country.

Reference Transform

A function used to transform the operand into the reference annotation stripped form. I.e. remove any annotation used to detect the reference type. E.g. "$Reference" => "Reference".

referenceTransform: (operand: string) => string

Default reference transform: It removes the $ symbol at the begging of the operand name.

Operator Mapping

Mapping of the operators. The key is unique operator key, and the value is the key used to represent the given operator in the raw expression.

operatorMapping: Map<symbol, string>

Default operator mapping:

// Comparison
;[Operator.EQ, '==']
;[Operator.NE, '!=']
;[(Operator.GT, '>')]
;[(Operator.GE, '>=')]
;[(Operator.LT, '<')]
;[(Operator.LE, '<=')]
;[(Operator.IN, 'IN')]
;[(Operator.NOT_IN, 'NOT IN')]
;[(Operator.PREFIX, 'PREFIX')]
;[(Operator.SUFFIX, 'SUFFIX')]
;[(Operator.OVERLAP, 'OVERLAP')]
;[(Operator.UNDEFINED, 'UNDEFINED')]
;[(Operator.PRESENT, 'PRESENT')]
// Logical
;[(Operator.AND, 'AND')]
;[(Operator.OR, 'OR')]
;[(Operator.NOR, 'NOR')]
;[(Operator.XOR, 'XOR')]
;[(Operator.NOT, 'NOT')]

The operator keys are unique symbols which could be imported from the engine package:

import type { Operator } from '@spaceavocado/illogical'

Contributing

See contributing.md.

License

Illogical is released under the MIT license. See license.txt.