packages feed

phino-0.0.145: test/Fixtures.hs

{-# LANGUAGE OverloadedStrings #-}

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

module Fixtures
  ( defaultReduceContext
  , explainPack
  , fixtureLambdas
  , lambdasFile
  , linked
  , loopingLambdas
  , overdue
  , primitives
  , readProtocol
  , readUtf8
  , recorded
  , recorded'
  , withLambdas
  , withLambdasOf
  , withTemp
  )
where

import AST (Expression (ExRoot))
import CLI.Helpers (withEvalFunc)
import CLI.Types (IOFormat (PHI), PrintContext (PrintCtx))
import Compiled (compiled)
import Control.Exception (bracket, evaluate)
import Data.Aeson (FromJSON (parseJSON), withObject, (.:))
import Data.ByteString qualified as BS
import Data.Char (toLower)
import Data.List (isPrefixOf, stripPrefix)
import Data.Map.Strict qualified as Map
import Data.Maybe (fromMaybe)
import Data.Text qualified as T
import Data.Text.Encoding (encodeUtf8)
import Data.Yaml qualified as Yaml
import Dataize (reduction)
import Deps (Judgment (..), SaveEvalFunc, dontSaveEval, dontSaveStep)
import Engine (Engine, building, yaml)
import Evaluate (evaluation, fired)
import GHC.Clock (getMonotonicTime)
import Lambdas (Lambdas, emptyLambdas, readLambdas)
import Lining (LineFormat (MULTILINE))
import Morph (Deadline (..), ReduceContext (..), Steps (..))
import Sugar (SugarType (SWEET))
import System.Directory (getTemporaryDirectory, removePathForcibly)
import System.FilePath (takeExtension)
import System.IO (Handle, IOMode (ReadMode), hClose, hGetContents, hSetEncoding, openBinaryTempFile, utf8, withFile)
import XMIR (defaultXmirContext)

defaultReduceContext :: Expression -> ReduceContext
defaultReduceContext loc = ReduceContext loc loc Nothing 25 25 (Steps 250 0) Nothing Nothing Nothing 1 False True False False 1 Nothing Morphing [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked

linked :: Engine
linked = fromMaybe yaml compiled

withLambdas :: Lambdas -> ReduceContext -> ReduceContext
withLambdas lambdas ctx = ctx{_symbolic = lambdas}

lambdasFile :: FilePath
lambdasFile = "test-resources/atoms.yaml"

fixtureLambdas :: IO Lambdas
fixtureLambdas = readLambdas lambdasFile

loopingLambdas :: (FilePath -> IO a) -> IO a
loopingLambdas = withLambdasOf "- λ: L_loop\n  𝑛: ⟦ λ ⤍ L_loop ⟧\n"

overdue :: Int -> IO Deadline
overdue cap = Deadline cap . subtract 1 <$> getMonotonicTime

withLambdasOf :: T.Text -> (FilePath -> IO a) -> IO a
withLambdasOf lambdas = withTemp "phino-symbolic-.yaml" (encodeUtf8 lambdas)

primitives :: String -> String
primitives src =
  unlines
    [ "[["
    , "  bytes -> [["
    , "    φ -> ?,"
    , "    not -> [[ ^ -> ?, L> L_bytes_not ]],"
    , "    eq -> [[ ^ -> ?, b -> ?, L> L_bytes_eq ]]"
    , "  ]],"
    , "  bool -> [["
    , "    φ -> ?,"
    , "    if -> [[ ^ -> ?, then -> ?, else -> ?, L> L_fork ]]"
    , "  ]],"
    , "  number -> [["
    , "    φ -> ?,"
    , "    as-bytes -> $.φ,"
    , "    plus -> [[ ^ -> ?, x -> ?, L> L_number_plus ]],"
    , "    times -> [[ ^ -> ?, x -> ?, L> L_number_times ]],"
    , "    div -> [[ ^ -> ?, x -> ?, L> L_number_div ]],"
    , "    gt -> [[ ^ -> ?, x -> ?, L> L_number_gt ]],"
    , "    eq -> [[ ^ -> ?, x -> ?, @ -> $.^.as-bytes.eq( x.as-bytes ) ]],"
    , "    nope -> [[ ^ -> ?, L> L_number_nope ]]"
    , "  ]],"
    , "  @ -> " ++ src
    , "]]"
    ]

recorded :: (SaveEvalFunc -> IO a) -> IO (a, String)
recorded = recorded' False

recorded' :: Bool -> (SaveEvalFunc -> IO a) -> IO (a, String)
recorded' hidden action =
  withTemp "phino-protocol-.txt" BS.empty $ \path -> do
    answer <- withEvalFunc (Just path) printing action
    written <- withoutTotals <$> readUtf8 path
    pure (answer, written)
  where
    printing :: PrintContext
    printing =
      PrintCtx
        SWEET
        hidden
        Nothing
        MULTILINE
        2
        defaultXmirContext
        False
        False
        False
        False
        False
        1
        1
        ExRoot
        Nothing
        Nothing
        Nothing
        PHI

newtype ExplainPack = ExplainPack String

instance FromJSON ExplainPack where
  parseJSON = withObject "ExplainPack" (\pack -> ExplainPack <$> pack .: "latex")

explainPack :: FilePath -> IO String
explainPack path = do
  ExplainPack latex <- Yaml.decodeFileThrow path
  pure latex

readUtf8 :: FilePath -> IO String
readUtf8 path =
  withFile path ReadMode $ \stream -> do
    hSetEncoding stream utf8
    content <- hGetContents stream
    _ <- evaluate (length content)
    pure content

readProtocol :: FilePath -> IO String
readProtocol path = sansTotals <$> readUtf8 path
  where
    sansTotals :: String -> String
    sansTotals
      | map toLower (takeExtension path) == ".xml" = withoutWrapper
      | otherwise = withoutTotals

withoutTotals :: String -> String
withoutTotals text
  | [msec, firings, fps] <- drop (length ls - 3) ls
  , "msec(" `isPrefixOf` msec
  , "firings(" `isPrefixOf` firings
  , "fps(" `isPrefixOf` fps =
      unlines (take (length ls - 3) ls)
  | otherwise = text
  where
    ls = lines text

withoutWrapper :: String -> String
withoutWrapper text = case lines text of
  (decl : "<protocol>" : rest)
    | Just kept <- withoutRunTotals rest -> unlines (decl : map dedented kept)
  _ -> text
  where
    withoutRunTotals :: [String] -> Maybe [String]
    withoutRunTotals rest = case reverse rest of
      (closing : fps : firings : msec : kept)
        | closing == "</protocol>"
        , "<fps>" `isPrefixOf` dropWhile (== ' ') fps
        , "<firings>" `isPrefixOf` dropWhile (== ' ') firings
        , "<msec>" `isPrefixOf` dropWhile (== ' ') msec ->
            Just (reverse kept)
      _ -> Nothing
    dedented :: String -> String
    dedented line = fromMaybe line (stripPrefix "  " line)

withTemp :: String -> BS.ByteString -> (FilePath -> IO a) -> IO a
withTemp template content action = do
  dir <- getTemporaryDirectory
  bracket (openBinaryTempFile dir template) discarded $ \(path, handle) -> do
    BS.hPut handle content
    hClose handle
    action path
  where
    discarded :: (FilePath, Handle) -> IO ()
    discarded (path, handle) = hClose handle >> removePathForcibly path