packages feed

stgi-1.1: test/Testsuite/Test/Machine/Evaluate/Programs.hs

{-# LANGUAGE BangPatterns      #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}

-- | Tests of medium size, defined by terminating within a certain number of
-- steps (configured in 'defSpec').

-- These tests will be run with garbage collection enabled, and should have the
-- scope of small functions a Haskell beginner might play around with.
module Test.Machine.Evaluate.Programs (tests) where



import Data.Foldable

import           Stg.Machine.Types
import           Stg.Marshal
import           Stg.Parser.QuasiQuoter
import qualified Stg.Prelude            as Stg

import           Test.Machine.Evaluate.TestTemplates.MachineState
import qualified Test.Machine.Evaluate.TestTemplates.MarshalledValue as MVal
import           Test.Machine.Evaluate.TestTemplates.Util
import           Test.Orphans                                        ()
import           Test.QuickCheck.Modifiers
import           Test.Tasty



tests :: TestTree
tests = testGroup "Programs"
    [ add3
    , takeRepeat
    , fibonacci
    , testGroup "mean of a list"
        [ meanNaive
        , meanNaiveWithFoldl'
        , meanGood ]
    ]

add3 :: TestTree
add3 = machineStateTest defSpec
    { testName = "add3 x y z = x+y+z"
    , successPredicate = "main" `hasValue` (6 :: Integer)
    , source = [stg|
        add3 = \x y z -> case x of
            Int# i -> case y of
                Int# j -> case +# i j of
                    ij -> case z of
                        Int# k -> case +# ij k of
                            ijk -> Int# ijk;
                        badInt -> Error_add3_1 badInt;
                badInt -> Error_add3_2 badInt;
            badInt -> Error_add3_3 badInt;

        one   = \ -> Int# 1#;
        two   = \ -> Int# 2#;
        three = \ -> Int# 3#;
        main = \ => add3 one two three
        |] }

takeRepeat :: TestTree
takeRepeat = machineStateTest defSpec
    { testName = "take 2 (repeat ())"
    , successPredicate = "twoUnits" `hasValue` replicate 2 ()
    , source = mconcat
        [ toStg "two" (2 :: Integer)
        , Stg.take
        , Stg.repeat
        , Stg.foldr
        , Stg.force
        , [stg|
        consBang = \x xs -> case xs of v -> Cons x v;
        nil = \ -> Nil;
        forceSpine = \xs -> foldr consBang nil xs;

        twoUnits = \ =>
            letrec
                repeated = \(unit) => repeat unit;
                unit = \ -> Unit;
                take2 = \(repeated) => take two repeated
            in forceSpine take2;

        main = \ -> force twoUnits
        |] ]}

fibonacci :: TestTree
fibonacci = machineStateTest defSpec
    { testName = "Fibonacci sequence"
    , successPredicate = "main" `hasValue` take numFibos fibo
    , maxSteps = 10000
    , source = mconcat
        [ toStg "zero" (0 :: Int)
        , toStg "one" (1 :: Int)
        , toStg "numFibos" (numFibos :: Int)
        , Stg.add
        , Stg.take
        , Stg.zipWith
        , Stg.force
        , [stg|
        main = \ =>
            letrec
                fibos = \(fibo) => take numFibos fibo;
                fibo = \ =>
                    letrec
                        fib0 = \(fib1) -> Cons zero fib1;
                        fib1 = \(fib2) -> Cons one fib2;
                        fib2 = \(fib0 fib1) => zipWith add fib0 fib1
                    in fib0
            in force fibos
        |] ]}
  where
    fibo :: [Integer]
    fibo = 0 : 1 : zipWith (+) fibo (tail fibo)
    numFibos :: Num a => a
    numFibos = 10

meanTestTemplate :: MVal.MarshalledValueTestSpec (NonEmptyList Integer) Integer
meanTestTemplate =
    let mean :: [Integer] -> Integer
        mean xs = let (total, count) = foldl' go (0,0) xs
                      go (!t, !c) x = (t+x, c+1)
                  in total `div` count
    in MVal.MarshalledValueTestSpec
        { MVal.testName = "Mena test template"
        , MVal.maxSteps = 1024
        , MVal.failWithInfo = False
        , MVal.failPredicate = const False
        , MVal.sourceSpec = \(NonEmpty inputList) -> MVal.MarshalSourceSpec
            { MVal.resultVar = "main"
            , MVal.expectedValue = mean inputList
            , MVal.source = mconcat
                [ Stg.add
                , Stg.div
                , toStg "zero" (0 :: Int)
                , toStg "one"  (1 :: Int)
                , toStg "inputList" inputList
                , [stg| main = \ => mean inputList |] ]}}

meanNaive :: TestTree
meanNaive = MVal.marshalledValueTest meanTestTemplate
    { MVal.testName = "Naïve: foldl and lazy tuple"
    , MVal.sourceSpec = \inputList -> (MVal.sourceSpec meanTestTemplate inputList)
        { MVal.source = mconcat
            [ MVal.source (MVal.sourceSpec meanTestTemplate inputList)
            , Stg.foldl
            , [stg|
            mean = \xs ->
                letrec
                    totals = \(go zeroTuple) -> foldl go zeroTuple;
                    zeroTuple = \ -> Tuple zero zero;
                    go = \acc x -> case acc of
                        Tuple t n ->
                            let tx = \(t x) => add t x;
                                n1 = \(n) => add n one
                            in Tuple tx n1;
                        badTuple -> Error_mean1 badTuple
                in case totals xs of
                    Tuple t n -> div t n;
                    badTuple -> Error_mean2 badTuple
            |] ]}}

meanNaiveWithFoldl' :: TestTree
meanNaiveWithFoldl' = MVal.marshalledValueTest meanTestTemplate
    { MVal.testName = "Naïve with insufficient optimization: foldl'"
    , MVal.sourceSpec = \inputList -> (MVal.sourceSpec meanTestTemplate inputList)
        { MVal.source = mconcat
            [ MVal.source (MVal.sourceSpec meanTestTemplate inputList)
            , Stg.foldl'
            , [stg|
            mean = \xs ->
                letrec
                    totals = \(go zeroTuple) -> foldl' go zeroTuple;
                    zeroTuple = \ -> Tuple zero zero;
                    go = \acc x -> case acc of
                        Tuple t n ->
                            let tx = \(t x) => add t x;
                                n1 = \(n) => add n one
                            in Tuple tx n1;
                        badTuple -> Error_mean1 badTuple
                in case totals xs of
                    Tuple t n -> div t n;
                    badTuple -> Error_mean2 badTuple
            |] ]}}

meanGood :: TestTree
meanGood = MVal.marshalledValueTest meanTestTemplate
    { MVal.testName = "Proper: foldl' and strict tuple"
    , MVal.failWithInfo = False
    , MVal.failPredicate = \stgState -> length (stgStack stgState) >= 9
    , MVal.sourceSpec = \inputList -> (MVal.sourceSpec meanTestTemplate inputList)
        { MVal.source = mconcat
            [ MVal.source (MVal.sourceSpec meanTestTemplate inputList)
            , Stg.foldl'
            , [stg|
            mean = \xs ->
                letrec
                    totals = \(go zeroTuple) -> foldl' go zeroTuple;
                    zeroTuple = \ -> Tuple zero zero;
                    go = \acc x -> case acc of
                        Tuple t n ->
                            let tx = \(t x) => add t x;
                                n1 = \(n) => add n one
                            in case tx of
                                default -> case n1 of
                                    default -> Tuple tx n1;
                        badTuple -> Error_mean1 badTuple
                in case totals xs of
                    Tuple t n -> div t n;
                    badTuple -> Error_mean2 badTuple
            |] ]}}