packages feed

phino-0.0.0.8: src/Condition.hs

-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT

module Condition where

import Ast
import Builder (buildAttribute, buildBinding)
import Data.Aeson (FromJSON)
import qualified Data.Map.Strict as M
import GHC.IO (unsafePerformIO)
import Matcher
import Misc (allPathsIn)
import Pretty (prettyExpression, prettySubsts)
import Yaml (normalizationRules)
import qualified Yaml as Y

-- Check if given attribute is present in given binding
attrInBindings :: Attribute -> [Binding] -> Bool
attrInBindings attr (bd : bds) = attrInBinding attr bd || attrInBindings attr bds
  where
    attrInBinding :: Attribute -> Binding -> Bool
    attrInBinding attr (BiTau battr _) = attr == battr
    attrInBinding attr (BiVoid battr) = attr == battr
    attrInBinding AtLambda (BiLambda _) = True
    attrInBinding AtDelta (BiDelta _) = True
    attrInBinding _ _ = False
attrInBindings _ _ = False

-- Apply 'eq' yaml condition to attributes
compareAttrs :: Attribute -> Attribute -> Subst -> Bool
compareAttrs (AtMeta left) (AtMeta right) _ = left == right
compareAttrs attr (AtMeta meta) (Subst mp) = case M.lookup meta mp of
  Just (MvAttribute found) -> attr == found
  _ -> False
compareAttrs (AtMeta meta) attr (Subst mp) = case M.lookup meta mp of
  Just (MvAttribute found) -> attr == found
  _ -> False
compareAttrs left right subst = right == left

-- Convert Number to Integer
numToInt :: Y.Number -> Subst -> Maybe Integer
numToInt (Y.Ordinal (AtMeta meta)) (Subst mp) = case M.lookup meta mp of
  Just (MvAttribute (AtAlpha idx)) -> Just idx
  _ -> Nothing
numToInt (Y.Ordinal (AtAlpha idx)) subst = Just idx
numToInt (Y.Length (BiMeta meta)) (Subst mp) = case M.lookup meta mp of
  Just (MvBindings bds) -> Just (toInteger (length bds))
  _ -> Nothing
numToInt (Y.Add left right) subst = case (numToInt left subst, numToInt right subst) of
  (Just left_, Just right_) -> Just (left_ + right_)
  _ -> Nothing
numToInt (Y.Literal num) subst = Just num
numToInt _ _ = Nothing

meetCondition' :: Y.Condition -> Subst -> [Subst]
meetCondition' (Y.Or []) subst = [subst]
meetCondition' (Y.Or (cond : rest)) subst = do
  let met = meetCondition' cond subst
  if null met
    then meetCondition' (Y.Or rest) subst
    else met
meetCondition' (Y.And []) subst = [subst]
meetCondition' (Y.And (cond : rest)) subst = do
  let met = meetCondition' cond subst
  if null met
    then []
    else meetCondition' (Y.And rest) subst
meetCondition' (Y.Not cond) subst = do
  let met = meetCondition' cond subst
  [subst | null met]
meetCondition' (Y.In attr binding) subst =
  case (buildAttribute attr subst, buildBinding binding subst) of
    (Just attr, Just bds) -> [subst | attrInBindings attr bds] -- if attrInBindings attr bd then [subst] else []
    (_, _) -> []
meetCondition' (Y.Alpha (AtAlpha _)) subst = [subst]
meetCondition' (Y.Alpha (AtMeta name)) (Subst mp) = case M.lookup name mp of
  Just (MvAttribute (AtAlpha _)) -> [Subst mp]
  _ -> []
meetCondition' (Y.Alpha _) _ = []
meetCondition' (Y.Eq (Y.CmpNum left) (Y.CmpNum right)) subst = case (numToInt left subst, numToInt right subst) of
  (Just left_, Just right_) -> [subst | left_ == right_]
  (_, _) -> []
meetCondition' (Y.Eq (Y.CmpAttr left) (Y.CmpAttr right)) subst = [subst | compareAttrs left right subst]
meetCondition' (Y.Eq _ _) _ = []
-- @todo #89:30min Extend list of expressions. There are expressions where we can definitely say
--  if this expression in normal form or not. In common case the expression in normal form if
--  it does not match with any of normalization rules. But it's quite expensive operation comparing to
--  simple list filtering and pattern matching. For example if expression is formation where all bindings are
--  void, lambda, or delta - this expression in normal form and there's no need to try to match it with normalization
--  rules. So we need find more such cases and introduce them.
meetCondition' (Y.NF (ExMeta meta)) (Subst mp) = case M.lookup meta mp of
  Just (MvExpression expr) -> case expr of
    ExThis -> [Subst mp]
    ExGlobal -> [Subst mp]
    ExTermination -> [Subst mp]
    ExDispatch ExThis _ -> [Subst mp]
    ExDispatch ExGlobal _ -> [Subst mp]
    ExDispatch ExTermination _ -> [] -- dd rule
    ExApplication ExTermination _ -> [] -- dc rule
    ExFormation [] -> [Subst mp]
    _ -> [Subst mp | not (matchesAnyNormalizationRule expr normalizationRules)]
  _ -> []
  where
    -- Returns True if given expression matches with any of given normalization rules
    matchesAnyNormalizationRule :: Expression -> [Y.Rule] -> Bool
    matchesAnyNormalizationRule _ [] = False
    matchesAnyNormalizationRule expr (rule : rules) =
      case matchProgramWithCondition (Y.pattern rule) (Y.when rule) (Program expr) of
        Just matched -> not (null matched) || matchesAnyNormalizationRule expr rules
        Nothing -> matchesAnyNormalizationRule expr rules
meetCondition' (Y.NF _) _ = []
meetCondition' (Y.FN (ExMeta meta)) (Subst mp) = case M.lookup meta mp of
  Just (MvExpression expr) -> meetCondition' (Y.FN expr) (Subst mp)
  _ -> []
meetCondition' (Y.FN (ExFormation _)) subst = [subst]
meetCondition' (Y.FN ExThis) subst = []
meetCondition' (Y.FN ExGlobal) subst = [subst]
meetCondition' (Y.FN (ExApplication expr (BiTau attr texpr))) subst = do
  let onExpr = meetCondition' (Y.FN expr) subst
      onTau = meetCondition' (Y.FN texpr) subst
  [subst | not (null onExpr) && not (null onTau)]
meetCondition' (Y.FN (ExDispatch expr _)) subst = meetCondition' (Y.FN expr) subst

-- For each substitution check if it meetCondition to given condition
-- If substitution does not meet the condition - it's thrown out
-- and is not used in replacement
meetCondition :: Y.Condition -> [Subst] -> [Subst]
meetCondition _ [] = []
meetCondition cond (subst : rest) = do
  let first = meetCondition' cond subst
  let next = meetCondition cond rest
  if null first
    then next
    else head first : next

-- Returns Just [...] if
-- 1. program matches pattern and
-- 2.1. condition is not present, or
-- 2.2. condition is present and met
-- Otherwise returns Nothing
matchProgramWithCondition :: Expression -> Maybe Y.Condition -> Program -> Maybe [Subst]
matchProgramWithCondition ptn condition program = do
  let matched = matchProgram ptn program
  if null matched
    then Nothing
    else case condition of
      Nothing -> Just matched
      Just cond -> do
        let met = meetCondition cond matched
        if null met
          then Nothing
          else Just met