packages feed

hbf-0.1.0.0: tests/CompilerTest.hs

module CompilerTest where

import           Control.Monad.Trans.State (execStateT)
import qualified Data.Text.Lazy.IO         as TIO
import           Hedgehog
import qualified Hedgehog                  as H
import           Hedgehog.Checkers         as HC
import           Test.HUnit

import           HBF.Compiler
import qualified HBF.Eval                  as E
import           HBF.Types
import           Helper

unit_fusionOptimization :: Assertion
unit_fusionOptimization = fusionOpt (Program p) @?= Program expected
  where
    p =
      [ Inc 2 1
      , Inc (-1) 1
      , Move 3
      , Move (-1)
      , Inc 1 0
      , In 1 0
      , In 2 0
      , Out 3 0
      , Out 4 0
      , Inc 2 0
      , Inc (-2) 0 -- should eliminate this
      , Out 1 0 --should not fuse the next two, but should fuse this one with the previous Out
      , Out 1 1
      , Out 1 2
      , Loop
          [ Inc 1 0
          , Inc 2 0
          , Loop [Move 1, Move 2, Clear 0, Clear 0, Clear 1, Clear 2]
          , Loop [Move 1, Move (-1)] -- should eliminate this whole loop
          , Scan Up 0
          , Scan Up 0
          , Scan Down 3
          , Scan Down 3
          , Scan Up 0 -- shouldn't fuse the next two
          , Scan Up 3
          , Scan Down 0 -- shouldn't fuse the next two
          , Scan Down 3
          ]
      ]
    expected =
      [ Inc 1 1
      , Move 2
      , Inc 1 0
      , In 3 0
      , Out 8 0
      , Out 1 1
      , Out 1 2
      , Loop
          [ Inc 3 0
          , Loop [Move 3, Clear 0, Clear 1, Clear 2]
          , Scan Up 0
          , Scan Down 3
          , Scan Up 0
          , Scan Up 3
          , Scan Down 0
          , Scan Down 3
          ]
      ]

unit_optimizationsDontChangeResults :: Assertion
unit_optimizationsDontChangeResults = do
  code <- TIO.readFile "tests/factor.bf"
  let (Right (optimized, _)) = inMemoryCompile defaultCompilerOptions code
  let (Right (unoptimized, _)) =
        inMemoryCompile noOptimizationCompilerOptions code
  a <- exec optimized
  b <- exec unoptimized
  a @?= b
  where
    exec program = execStateT (E.eval program) (mkMockIOS "25454\n")

fullyFused :: Program o -> Bool
fullyFused p = all (uncurry fused) (zip ops (tail ops))
  where
    ops = instructions p
    fused (Inc _ n) (Inc _ m)
      | n == m = False
    fused (Move _) (Move _) = False
    fused (In _ n) (In _ m)
      | n == m = False
    fused (Out _ n) (Out _ m)
      | n == m = False
    fused (Clear n) (Clear m)
      | n == m = False
    fused (Loop inner) _ = fullyFused $ Program inner
    fused _ (Loop inner) = fullyFused $ Program inner
    fused _ _ = True

unit_fullyFusedTest :: Assertion
unit_fullyFusedTest =
  not (fullyFused $ Program [Inc 1 1, Inc 4 1]) &&
  not (fullyFused $ Program [Inc 1 0, Loop [Inc 1 1, Loop [Move 1, Move 2]]]) &&
  fullyFused (Program [Inc 1 0, Move 2]) @=? True

hprop_fusionDoesntLeaveAnythingToBeFused :: Property
hprop_fusionDoesntLeaveAnythingToBeFused =
  property $ do
    program <- forAll programGen
    let optimized = fusionOpt . toIR $ program
    H.assert $ all noNoOp (flattened optimized) && fullyFused optimized
  where
    noNoOp (Inc n _)    = n /= 0
    noNoOp (Move n)     = n /= 0
    noNoOp (In n _)     = n /= 0
    noNoOp (Out n _)    = n /= 0
    noNoOp (Clear _)    = True
    noNoOp (Mul _ _ to) = to /= 0
    noNoOp (Scan _ _)   = True
    noNoOp (Loop _)     = error "noNoOp: unexpected operation"

hprop_FusedProgramHasValidMonoid :: Property
hprop_FusedProgramHasValidMonoid = property $ HC.monoid programGen

unit_clearOptimization :: Assertion
unit_clearOptimization = clearOpt (Program p) @?= Program expected
  where
    p =
      [ Inc 2 0
      , Inc (-1) 0
      , Loop [Inc (-1) 0]
      , Loop
          [ Inc 1 0
          , Loop [Inc (-1) 0]
          , Loop [Loop [Inc (-1) 0], Move 3, Loop [Inc (-1) 0]]
          ]
      ]
    expected =
      [ Inc 2 0
      , Inc (-1) 0
      , Clear 0
      , Loop [Inc 1 0, Clear 0, Loop [Clear 0, Move 3, Clear 0]]
      ]

unit_mulOptimization :: Assertion
unit_mulOptimization = mulOpt (Program p) @?= Program expected
  where
    p =
      [ Inc 2 0
      , Inc (-1) 0
      , makeMul [(1, 2)]
      , Loop [Inc (-1) 0, makeMul [(2, 4), (4, 5)], Inc 1 0]
      , Loop [Inc (-1) 0, Move 1, Inc 1 0, Move (-1), Inc 1 0] {- this extra part breaks the multiplication loop -}
         -- this tests missing eof
      ]
    expected =
      [ Inc 2 0
      , Inc (-1) 0
      , Mul 1 0 2
      , Clear 0
      , Loop [Inc (-1) 0, Mul 2 0 4, Mul 4 0 9, Clear 0, Inc 1 0]
      , Loop [Inc (-1) 0, Move 1, Inc 1 0, Move (-1), Inc 1 0]
      ]

unit_scanOptimization :: Assertion
unit_scanOptimization = scanOpt (Program p) @?= Program expected
  where
    p =
      [ Inc 2 0
      , Inc (-1) 0
      , Loop [Move (-1)]
      , Loop [Inc (-1) 0, Loop [Move 1], Loop [Move 2], Inc 1 0]
      ]
    expected =
      [ Inc 2 0
      , Inc (-1) 0
      , Scan Down 0
      , Loop [Inc (-1) 0, Scan Up 0, Loop [Move 2], Inc 1 0]
      ]

unit_offsetInstructionsOptimization :: Assertion
unit_offsetInstructionsOptimization =
  offsetInstructionOpt (Program p) @?= Program expected
  where
    p =
      [ Inc 2 0
      , Move 1
      , Inc (-1) 0
      , Move 2
      , Mul 3 0 2
      , Loop [Move (-1)]
      , Loop
          [ Inc (-1) 0
          , Move (-1)
          , In 1 0
          , Loop [Move 2, Inc 3 0, Move (-1)]
          , Inc 1 0
          ]
      ]
    expected =
      [ Inc 2 0
      , Inc (-1) 1
      , Mul 3 3 2
      , Move 3
      , Loop [Move (-1)]
      , Loop [Inc (-1) 0, In 1 (-1), Move (-1), Loop [Inc 3 2, Move 1], Inc 1 0]
      ]