packages feed

jsonlogic-hs-0.1.0.0: src/JsonLogic/Core/Operator.hs

{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      : JsonLogic.Core.Operator
--
-- The names of operators, and the closed set of builtin ones.
--
-- This module is the single source of truth for which operator names are
-- builtin. The parser, the custom-operator registry (which refuses builtin
-- names), the test generators and the conformance runners all take the set
-- from 'allBuiltins', so adding an operator is one constructor here plus the
-- cases the compiler then demands, never an edit to a list kept elsewhere.
module JsonLogic.Core.Operator
  ( OperatorName (..)
  , Builtin (..)
  , allBuiltins
  , builtinName
  , builtinOperatorName
  , lookupBuiltin
  ) where

import qualified Data.Map.Strict as Map
import Data.Text (Text)

-- | An operator's name, as it appears as the only key of a rule object.
-- A newtype, so it cannot be confused with a variable path or any other
-- string.
newtype OperatorName = OperatorName Text
  deriving (Eq, Ord, Show)

-- | The operators of the classic JsonLogic specification
-- (<https://jsonlogic.com/operations.html>), as implemented by the reference,
-- json-logic-js 2.0.5. @?:@ is the reference's alias of @if@ and has its own
-- constructor because it has its own name.
data Builtin
  = BVar
  | BMissing
  | BMissingSome
  | BIf
  | BTernary
  | BAnd
  | BOr
  | BNot
  | BDoubleNot
  | BLooseEq
  | BStrictEq
  | BLooseNeq
  | BStrictNeq
  | BLt
  | BLte
  | BGt
  | BGte
  | BMax
  | BMin
  | BAdd
  | BSub
  | BMul
  | BDiv
  | BMod
  | BMap
  | BFilter
  | BReduce
  | BAll
  | BNone
  | BSome
  | BMerge
  | BIn
  | BCat
  | BSubstr
  | BLog
  deriving (Eq, Ord, Show, Enum, Bounded)

-- | Every builtin operator, in declaration order.
allBuiltins :: [Builtin]
allBuiltins = [minBound .. maxBound]

-- | The name a rule uses for a builtin.
builtinName :: Builtin -> Text
builtinName builtin = case builtin of
  BVar -> "var"
  BMissing -> "missing"
  BMissingSome -> "missing_some"
  BIf -> "if"
  BTernary -> "?:"
  BAnd -> "and"
  BOr -> "or"
  BNot -> "!"
  BDoubleNot -> "!!"
  BLooseEq -> "=="
  BStrictEq -> "==="
  BLooseNeq -> "!="
  BStrictNeq -> "!=="
  BLt -> "<"
  BLte -> "<="
  BGt -> ">"
  BGte -> ">="
  BMax -> "max"
  BMin -> "min"
  BAdd -> "+"
  BSub -> "-"
  BMul -> "*"
  BDiv -> "/"
  BMod -> "%"
  BMap -> "map"
  BFilter -> "filter"
  BReduce -> "reduce"
  BAll -> "all"
  BNone -> "none"
  BSome -> "some"
  BMerge -> "merge"
  BIn -> "in"
  BCat -> "cat"
  BSubstr -> "substr"
  BLog -> "log"

-- | 'builtinName' as an 'OperatorName'.
builtinOperatorName :: Builtin -> OperatorName
builtinOperatorName = OperatorName . builtinName

-- | The builtin with exactly this name, if there is one. There is no
-- trimming, no case folding and no dotted-path resolution.
lookupBuiltin :: Text -> Maybe Builtin
lookupBuiltin name = Map.lookup name byName

byName :: Map.Map Text Builtin
byName = Map.fromList [(builtinName builtin, builtin) | builtin <- allBuiltins]