feat: tests for the rendering/parsing

This commit is contained in:
vegowotenks 2025-06-20 09:26:11 +02:00
parent 9484d097d4
commit d012307d19
8 changed files with 104 additions and 45 deletions

View file

@ -29,6 +29,7 @@ library
Language.Brainfuck.Instruction Language.Brainfuck.Instruction
Language.Brainfuck.Instruction.Compressed Language.Brainfuck.Instruction.Compressed
Language.Brainfuck.Instruction.Extended Language.Brainfuck.Instruction.Extended
Language.Brainfuck.Interpreter
other-modules: other-modules:
Paths_bf_optimize Paths_bf_optimize
autogen-modules: autogen-modules:
@ -37,7 +38,9 @@ library
src src
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
build-depends: build-depends:
base >=4.7 && <5 QuickCheck
, base >=4.7 && <5
, quickcheck-instances
, text , text
, vector , vector
default-language: Haskell2010 default-language: Haskell2010
@ -52,8 +55,10 @@ executable bf-optimize-exe
app app
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
build-depends: build-depends:
base >=4.7 && <5 QuickCheck
, base >=4.7 && <5
, bf-optimize , bf-optimize
, quickcheck-instances
, text , text
, vector , vector
default-language: Haskell2010 default-language: Haskell2010
@ -62,8 +67,8 @@ test-suite bf-optimize-test
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
main-is: Spec.hs main-is: Spec.hs
other-modules: other-modules:
Arbitrary.InvalidBrainfuckProgram Arbitrary.InvalidBrainfuckText
Arbitrary.ValidBrainfuckProgram Arbitrary.ValidBrainfuckText
Paths_bf_optimize Paths_bf_optimize
autogen-modules: autogen-modules:
Paths_bf_optimize Paths_bf_optimize
@ -74,6 +79,7 @@ test-suite bf-optimize-test
QuickCheck QuickCheck
, base >=4.7 && <5 , base >=4.7 && <5
, bf-optimize , bf-optimize
, quickcheck-instances
, text , text
, vector , vector
default-language: Haskell2010 default-language: Haskell2010

View file

@ -23,6 +23,8 @@ dependencies:
- base >= 4.7 && < 5 - base >= 4.7 && < 5
- text - text
- vector - vector
- QuickCheck
- quickcheck-instances
ghc-options: ghc-options:
- -Wall - -Wall
@ -59,4 +61,3 @@ tests:
- -with-rtsopts=-N - -with-rtsopts=-N
dependencies: dependencies:
- bf-optimize - bf-optimize
- QuickCheck

View file

@ -1,7 +1,8 @@
{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
module Language.Brainfuck (parse, ParseFailure) where {-# LANGUAGE LambdaCase #-}
module Language.Brainfuck (parse, ParseFailure, render) where
import Control.Monad ((<$!>)) import Control.Monad ((<$!>))
import Control.Monad.ST (runST, ST) import Control.Monad.ST (runST, ST)
@ -14,11 +15,12 @@ import Data.Vector qualified as Vector
import Data.Vector.Mutable qualified as MutableVector import Data.Vector.Mutable qualified as MutableVector
import Language.Brainfuck.Instruction ( Instruction(..) ) import Language.Brainfuck.Instruction ( Instruction(..) )
import qualified Data.List as List
data ParseFailure data ParseFailure
= UnexpectedClosingBracket Int -- closing bracket position = UnexpectedClosingBracket Int -- closing bracket position
| UnmatchedOpenBracket Int -- opening bracket position | UnmatchedOpenBracket Int -- opening bracket position
deriving stock (Show) deriving stock (Show, Eq)
-- | Convert a Text to a list of instructions, discard all comments. -- | Convert a Text to a list of instructions, discard all comments.
-- --
@ -83,3 +85,21 @@ parse text = runST $ do
-- >>> parse $ Text.pack "[]]" -- >>> parse $ Text.pack "[]]"
-- Left (UnexpectedClosingBracket 2) -- Left (UnexpectedClosingBracket 2)
render :: Vector Instruction -> Text
render = Text.concat . List.map renderSingle . Vector.toList
where
renderSingle = \case
Increment -> Text.singleton '+'
Decrement -> Text.singleton '-'
MoveLeft -> Text.singleton '<'
MoveRight -> Text.singleton '>'
ReadByte -> Text.singleton ','
PutByte -> Text.singleton '.'
Loop body -> Text.concat [Text.singleton '[', render body, Text.singleton ']']
-- >>> import Test.QuickCheck.Instances.Vector ()
-- >>> import Test.QuickCheck.Arbitrary (arbitrary)
-- >>> render <$> Gen.generate arbitrary
-- "<,<>,<,."

View file

@ -1,7 +1,13 @@
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE InstanceSigs #-}
module Language.Brainfuck.Instruction (Instruction(..)) where module Language.Brainfuck.Instruction (Instruction(..)) where
import Data.Vector ( Vector ) import Data.Vector ( Vector )
import Test.QuickCheck.Gen (Gen)
import Test.QuickCheck.Arbitrary (Arbitrary (arbitrary))
import Test.QuickCheck.Instances.Vector ()
import qualified Test.QuickCheck.Gen as Gen
data Instruction data Instruction
= Increment = Increment
@ -12,3 +18,20 @@ data Instruction
| PutByte | PutByte
| Loop (Vector Instruction) | Loop (Vector Instruction)
deriving stock (Show, Eq) deriving stock (Show, Eq)
instance Arbitrary Instruction where
arbitrary :: Gen Instruction
arbitrary = Gen.oneof $
[ pure Increment
, pure Decrement
, pure MoveLeft
, pure MoveRight
, pure ReadByte
, pure PutByte
, Loop <$> reduceSize 8 arbitrary
]
where
reduceSize d g = Gen.sized $ \ s -> Gen.resize (s `div` d) g
-- >>> Gen.generate (Gen.resize 30 arbitrary) :: IO [Instruction]
-- [MoveLeft,Decrement,PutByte,Decrement,Decrement,Increment,ReadByte,MoveLeft,Increment,Loop [],MoveRight,Increment,Increment,Loop [],MoveLeft,Loop [],MoveRight,ReadByte,ReadByte,MoveLeft,Decrement,MoveRight,MoveLeft,Loop [PutByte,Increment,Loop []],Decrement]

View file

@ -1,26 +0,0 @@
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE DerivingStrategies #-}
module Arbitrary.InvalidBrainfuckProgram (InvalidBrainfuckProgram(..)) where
import Data.Text (Text)
import Test.QuickCheck.Gen (Gen)
import Test.QuickCheck.Arbitrary (Arbitrary (arbitrary))
import qualified Data.Text as Text
import qualified Test.QuickCheck.Gen as Gen
import qualified Arbitrary.ValidBrainfuckProgram as ValidBrainfuckProgram
newtype InvalidBrainfuckProgram = InvalidBrainfuckProgram { get :: Text }
deriving stock Show
instance Arbitrary InvalidBrainfuckProgram where
arbitrary :: Gen InvalidBrainfuckProgram
arbitrary = do
validProgram <- ValidBrainfuckProgram.get <$> arbitrary
index <- Gen.choose (0, Text.length validProgram)
badBrace <- Gen.oneof $ map pure "[]"
let (prefix, suffix) = Text.splitAt index validProgram
pure . InvalidBrainfuckProgram $ Text.concat [prefix, Text.singleton badBrace, suffix]

View file

@ -0,0 +1,26 @@
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE DerivingStrategies #-}
module Arbitrary.InvalidBrainfuckText (InvalidBrainfuckText(..)) where
import Data.Text (Text)
import Test.QuickCheck.Gen (Gen)
import Test.QuickCheck.Arbitrary (Arbitrary (arbitrary))
import qualified Data.Text as Text
import qualified Test.QuickCheck.Gen as Gen
import qualified Arbitrary.ValidBrainfuckText as ValidBrainfuckText
newtype InvalidBrainfuckText = InvalidBrainfuckText { get :: Text }
deriving stock Show
instance Arbitrary InvalidBrainfuckText where
arbitrary :: Gen InvalidBrainfuckText
arbitrary = do
validText <- ValidBrainfuckText.get <$> arbitrary
index <- Gen.choose (0, Text.length validText)
badBrace <- Gen.oneof $ map pure "[]"
let (prefix, suffix) = Text.splitAt index validText
pure . InvalidBrainfuckText $ Text.concat [prefix, Text.singleton badBrace, suffix]

View file

@ -1,6 +1,6 @@
{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
module Arbitrary.ValidBrainfuckProgram (ValidBrainfuckProgram(..)) where module Arbitrary.ValidBrainfuckText (ValidBrainfuckText(..)) where
import Data.Text (Text) import Data.Text (Text)
import Test.QuickCheck.Arbitrary (Arbitrary (arbitrary)) import Test.QuickCheck.Arbitrary (Arbitrary (arbitrary))
import Test.QuickCheck.Gen (Gen, sized, frequency, oneof, resize) import Test.QuickCheck.Gen (Gen, sized, frequency, oneof, resize)
@ -8,11 +8,11 @@ import Data.Coerce (coerce)
import qualified Data.Text as Text import qualified Data.Text as Text
newtype ValidBrainfuckProgram = ValidBrainfuckProgram { get :: Text } newtype ValidBrainfuckText = ValidBrainfuckText { get :: Text }
deriving stock Show deriving stock Show
instance Arbitrary ValidBrainfuckProgram where instance Arbitrary ValidBrainfuckText where
arbitrary :: Gen ValidBrainfuckProgram arbitrary :: Gen ValidBrainfuckText
arbitrary = coerce . fmap Text.pack $ character [] arbitrary = coerce . fmap Text.pack $ character []
where where
character rest = do character rest = do
@ -42,6 +42,7 @@ instance Arbitrary ValidBrainfuckProgram where
pure $ commentChar : rest pure $ commentChar : rest
-- >>> import Test.QuickCheck.Gen (generate) -- >>> import Test.QuickCheck.Gen (generate)
-- >>> generate $ (arbitrary :: Gen ValidBrainfuckProgram) -- >>> generate $ (arbitrary :: Gen ValidBrainfuckText)
-- ValidBrainfuckProgram {get = "<-"} -- ValidBrainfuckText {get = "<[><]>[>],>+,[.-K.]+.>[<],z[p[[<]]+<],<[+]+[P[+][[e]]+,]+,.[-][[<],>>++]+"}

View file

@ -1,17 +1,25 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
import Data.Vector (Vector)
import Test.QuickCheck.All (quickCheckAll) import Test.QuickCheck.All (quickCheckAll)
import System.Exit (exitFailure, exitSuccess) import System.Exit (exitFailure, exitSuccess)
import Language.Brainfuck.Instruction (Instruction)
import qualified Data.Either as Either import qualified Data.Either as Either
import qualified Language.Brainfuck as Brainfuck import qualified Language.Brainfuck as Brainfuck
import qualified Arbitrary.ValidBrainfuckProgram as ValidBrainfuckProgram import qualified Arbitrary.ValidBrainfuckText as ValidBrainfuckText
import qualified Arbitrary.InvalidBrainfuckProgram as InValidBrainfuckProgram import qualified Arbitrary.InvalidBrainfuckText as InValidBrainfuckText
prop_acceptValidPrograms :: ValidBrainfuckProgram.ValidBrainfuckProgram -> Bool prop_acceptValidTexts :: ValidBrainfuckText.ValidBrainfuckText -> Bool
prop_acceptValidPrograms = Either.isRight . Brainfuck.parse . ValidBrainfuckProgram.get prop_acceptValidTexts = Either.isRight . Brainfuck.parse . ValidBrainfuckText.get
prop_rejectInvalidPrograms :: InValidBrainfuckProgram.InvalidBrainfuckProgram -> Bool prop_rejectInvalidTexts :: InValidBrainfuckText.InvalidBrainfuckText -> Bool
prop_rejectInvalidPrograms = Either.isLeft . Brainfuck.parse . InValidBrainfuckProgram.get prop_rejectInvalidTexts = Either.isLeft . Brainfuck.parse . InValidBrainfuckText.get
prop_renderParseInverse :: Vector Instruction -> Bool
prop_renderParseInverse source = Right source == (Brainfuck.parse . Brainfuck.render $ source)
return [] -- template haskell guard return [] -- template haskell guard
runQuickCheck :: IO Bool runQuickCheck :: IO Bool