packages feed

morley-0.7.0: test/Test/Parser.hs

module Test.Parser
  ( unit_Parse_contracts
  , unit_Parse_bad_contracts
  , unit_Value
  , unit_string_literal
  , unit_annotation
  , unit_IF
  , unit_MAP
  , unit_PAIR
  , unit_pair_type
  , unit_tuple_type
  , unit_or_type
  , unit_lambda_type
  , unit_list_type
  , unit_set_type
  , unit_Pair_constructor
  , unit_PrintComment
  , unit_ParserException
  , unit_letType
  ) where

import qualified Data.List.NonEmpty as NE
import Test.Hspec.Expectations (Expectation, expectationFailure, shouldBe, shouldSatisfy)
import Text.Megaparsec (parse)
import Text.Megaparsec.Error
  (ErrorFancy(ErrorCustom), ParseError(FancyError), bundleErrors, errorBundlePretty)

import Michelson.ErrorPos (srcPos)
import Michelson.Macro as Mo
import Michelson.Parser as P
import Michelson.Parser.Annotations as PA
import Michelson.Untyped as Mo
import Util.IO
import Util.Positive

import Test.Util.Contracts (getIllTypedContracts, getUnparsableContracts, getWellTypedContracts)

unit_Parse_contracts :: Expectation
unit_Parse_contracts = do
  files <- mappend <$> getWellTypedContracts <*> getIllTypedContracts
  mapM_ (checkFile True) files

unit_Parse_bad_contracts :: Expectation
unit_Parse_bad_contracts = do
  files <- getUnparsableContracts
  mapM_ (checkFile False) files

checkFile :: Bool -> FilePath -> Expectation
checkFile shouldParse file = do
  code <- readFileUtf8 file
  case parse P.program file code of
    Left err
      | shouldParse -> expectationFailure $ errorBundlePretty err
    Right _
      | not shouldParse -> expectationFailure $ "Managed to parse " <> file
    _ -> pass

unit_Value :: Expectation
unit_Value = do
  P.parseNoEnv P.value "" "{}" `shouldBe`
    Right Mo.ValueNil
  P.parseNoEnv P.value "" "{PUSH int 5;}" `shouldBe`
    (Right . ValueLambda $ NE.fromList
      [Mo.Prim (Mo.PUSH noAnn (Mo.Type (Mo.Tc Mo.CInt) noAnn) (Mo.ValueInt 5)) (srcPos 0 1)]
    )
  P.parseNoEnv P.value "" "{1; 2}" `shouldBe`
    (Right . Mo.ValueSeq $ NE.fromList
      [Mo.ValueInt 1, Mo.ValueInt 2]
    )
  P.parseNoEnv P.value "" "{Elt 1 2; Elt 3 4}" `shouldBe`
    (Right . Mo.ValueMap $ NE.fromList
      [Mo.Elt (Mo.ValueInt 1) (Mo.ValueInt 2), Mo.Elt (Mo.ValueInt 3) (Mo.ValueInt 4)]
    )
  P.parseNoEnv P.value "" "{DIP DROP;}" `shouldBe`
    Right (Mo.ValueLambda (Mo.Prim (Mo.DIP [Mo.Prim Mo.DROP (srcPos 0 5)]) (srcPos 0 1) :| []))
  P.parseNoEnv P.value "" "{DIP DROP;somecontent}" `shouldSatisfy` isLeft
  P.parseNoEnv P.value "" "{{ }; {}; {PUSH int 5; DROP}}" `shouldBe`
    Right (Mo.ValueLambda (Mo.Seq [] (srcPos 0 1)
            :| [ Mo.Seq [] (srcPos 0 6)
               , Mo.Seq [Mo.Prim (Mo.PUSH noAnn (Mo.Type (Mo.Tc Mo.CInt) noAnn) (Mo.ValueInt 5)) (srcPos 0 11)
                        , Mo.Prim Mo.DROP (srcPos 0 23)] (srcPos 0 10)
               ]
           ))
  P.parseNoEnv P.value "" "{{}; {}; {5}}" `shouldBe`
    Right (Mo.ValueSeq (Mo.ValueNil :| [Mo.ValueNil, Mo.ValueSeq (Mo.ValueInt 5 :| [])]))
  P.parseNoEnv P.value "" "{{}; {5}; {Push int 5}}" `shouldSatisfy` isLeft

unit_string_literal :: Expectation
unit_string_literal = do
  P.parseNoEnv P.stringLiteral "" "\"\"" `shouldSatisfy` isRight
  P.parseNoEnv P.stringLiteral "" "\" \\n  \"" `shouldSatisfy` isRight
  P.parseNoEnv P.stringLiteral "" "\"abacaba \\t \n\n\r a\"" `shouldSatisfy` isLeft
  P.parseNoEnv P.stringLiteral "" "\"abacaba \\t \\n\\n\\r" `shouldSatisfy` isLeft

unit_annotation :: Expectation
unit_annotation = do
  P.parseNoEnv PA.noteV "" "@" `shouldSatisfy` isRight
  P.parseNoEnv PA.noteV "" "@_" `shouldSatisfy` isRight
  P.parseNoEnv PA.noteV "" "@a." `shouldSatisfy` isRight
  P.parseNoEnv PA.noteV "" "@.a" `shouldSatisfy` isLeft
  P.parseNoEnv PA.noteV "" "@7a" `shouldSatisfy` isLeft
  P.parseNoEnv PA.noteV "" "@@@" `shouldSatisfy` isLeft
  P.parseNoEnv PA.noteV "" "@a b" `shouldSatisfy` isLeft
  P.parseNoEnv PA.noteV "" "@a\\" `shouldSatisfy` isLeft
  -- TODO [#48] these are special annotations and should not always be accepted
  P.parseNoEnv PA.noteV "" "@%" `shouldSatisfy` isRight
  P.parseNoEnv PA.noteV "" "@%%" `shouldSatisfy` isRight

unit_IF :: Expectation
unit_IF = do
  P.parseNoEnv P.codeEntry "" "{IF {} {};}" `shouldBe`
    Prelude.Right [Mo.Prim (Mo.IF [] []) (srcPos 0 1)]
  P.parseNoEnv P.codeEntry "" "{IFEQ {} {};}" `shouldBe`
    Prelude.Right [Mo.Mac (Mo.IFX (Mo.EQ noAnn) [] []) (srcPos 0 1)]
  P.parseNoEnv P.codeEntry "" "{IFCMPEQ {} {};}" `shouldBe`
    Prelude.Right [Mo.Mac (Mo.IFCMP (Mo.EQ noAnn) noAnn [] []) (srcPos 0 1)]

unit_MAP :: Expectation
unit_MAP = do
  parseNoEnv P.codeEntry "" "{MAP {};}" `shouldBe`
    Prelude.Right [Mo.Prim (Mo.MAP noAnn []) (srcPos 0 1)]
  parseNoEnv P.codeEntry "" "{MAP_CAR {};}" `shouldBe`
    Prelude.Right [Mo.Mac (Mo.MAP_CADR [Mo.A] noAnn noAnn []) (srcPos 0 1)]

unit_PAIR :: Expectation
unit_PAIR = do
  P.parseNoEnv P.codeEntry "" "{PAIR;}" `shouldBe`
    Prelude.Right [Mo.Prim (PAIR noAnn noAnn noAnn noAnn) (srcPos 0 1)]
  P.parseNoEnv P.codeEntry "" "{PAIR %a;}" `shouldBe`
    Prelude.Right [Mac (PAPAIR (P (F (noAnn, Mo.ann "a")) (F (noAnn,noAnn))) noAnn noAnn) (srcPos 0 1)]
  P.parseNoEnv P.codeEntry "" "{PAPAIR;}" `shouldBe`
    Prelude.Right
      [flip Mac (srcPos 0 1) $
        PAPAIR (P (F (noAnn,noAnn)) (P (F (noAnn,noAnn)) (F (noAnn,noAnn))))
          noAnn noAnn
      ]

unit_pair_type :: Expectation
unit_pair_type = do
  P.parseNoEnv P.type_ "" "pair unit unit" `shouldBe` Right unitPair
  P.parseNoEnv P.type_ "" "(unit, unit)" `shouldBe` Right unitPair
  P.parseNoEnv P.type_ "" "(key, (int, (string, bool)))"
    `shouldSatisfy` isRight
  P.parseNoEnv P.type_ "" "(signature, chain_id, string, bool)"
    `shouldSatisfy` isRight
  where
    unitPair :: Mo.Type
    unitPair =
      Mo.Type (Mo.TPair noAnn noAnn (Mo.Type Mo.TUnit noAnn) (Mo.Type Mo.TUnit noAnn)) noAnn

unit_tuple_type :: Expectation
unit_tuple_type = do
  P.parseNoEnv P.type_ "" "(int, int, bool, unit, nat)"
    `shouldBe` Right (typair (typair tyint tyint) (typair tybool (typair tyunit tynat)))
  P.parseNoEnv P.type_ ""
    "(pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat (pair nat nat))))))))))))))))"
      `shouldBe` Right (bigPair 16)
  where
    bigPair :: Integer -> Type
    bigPair n | n == 0 = tynat
    bigPair n          = typair tynat (bigPair (n - 1))

unit_or_type :: Expectation
unit_or_type = do
  P.parseNoEnv P.type_ "" "or unit unit" `shouldBe` Right unitOr
  P.parseNoEnv P.type_ "" "(unit | unit)" `shouldBe` Right unitOr
  P.parseNoEnv P.type_ "" "(chain_id | (int | (string | bool)))"
    `shouldSatisfy` isRight
  P.parseNoEnv P.type_ "" "or unit unit kek"
    `shouldSatisfy` isLeft
  where
    unitOr :: Mo.Type
    unitOr =
      Mo.Type (Mo.TOr noAnn noAnn (Mo.Type Mo.TUnit noAnn) (Mo.Type Mo.TUnit noAnn)) noAnn

unit_lambda_type :: Expectation
unit_lambda_type = do
  P.parseNoEnv P.type_ "" "lambda unit unit" `shouldBe` Right lambdaUnitUnit
  P.parseNoEnv P.type_ "" "\\unit -> unit" `shouldBe` Right lambdaUnitUnit
  P.parseNoEnv P.type_ "" "lambda int (signature, int)" `shouldSatisfy` isRight
  where
    lambdaUnitUnit :: Mo.Type
    lambdaUnitUnit =
      Mo.Type (Mo.TLambda (Mo.Type Mo.TUnit noAnn) (Mo.Type Mo.TUnit noAnn)) noAnn

unit_list_type :: Expectation
unit_list_type = do
  P.parseNoEnv P.type_ "" "list unit" `shouldBe` Right unitList
  P.parseNoEnv P.type_ "" "[unit]" `shouldBe` Right unitList
  P.parseNoEnv P.type_ "" "[(key, key)]" `shouldSatisfy` isRight
  where
    unitList :: Mo.Type
    unitList =
      Mo.Type (Mo.TList (Mo.Type Mo.TUnit noAnn)) noAnn

unit_set_type :: Expectation
unit_set_type = do
  P.parseNoEnv P.type_ "" "set int" `shouldBe` Right intSet
  P.parseNoEnv P.type_ "" "{int}" `shouldBe` Right intSet
  where
    intSet :: Mo.Type
    intSet =
      Mo.Type (Mo.TSet (Mo.Comparable Mo.CInt noAnn)) noAnn

unit_Pair_constructor :: Expectation
unit_Pair_constructor = do
  P.parseNoEnv P.value "" "Pair Unit Unit" `shouldBe` Right unitPair
  P.parseNoEnv P.value "" "(Unit, Unit)" `shouldBe` Right unitPair
  where
    unitPair :: Mo.Value' Mo.ParsedOp
    unitPair = Mo.ValuePair Mo.ValueUnit Mo.ValueUnit

unit_PrintComment :: Expectation
unit_PrintComment = do
  P.parseNoEnv P.printComment "" "\"Sides are %[0] x %[1]\"" `shouldBe`
    Right (PrintComment [Left "Sides are ", Right (StackRef 0), Left " x ", Right (StackRef 1)])
  P.parseNoEnv P.printComment "" "\"%[0] x\"" `shouldBe`
    Right (PrintComment [Right (StackRef 0), Left " x"])
  P.parseNoEnv P.printComment "" "\"%[0]x%[1]\"" `shouldBe`
    Right (PrintComment [Right (StackRef 0), Left "x", Right (StackRef 1)])
  P.parseNoEnv P.printComment "" "\"%[0]%[1]\"" `shouldBe`
    Right (PrintComment [Right (StackRef 0), Right (StackRef 1)])
  P.parseNoEnv P.printComment "" "\"xxx\"" `shouldBe`
    Right (PrintComment [Left "xxx"])
  P.parseNoEnv P.printComment "" "\"\"" `shouldBe`
    Right (PrintComment [])

unit_ParserException :: Expectation
unit_ParserException = do
  handleCustomError "0x000" P.value OddNumberBytesException
  handleCustomError "Right 0x000" P.value OddNumberBytesException
  handleCustomError "kek" P.type_ UnknownTypeException
  handleCustomError "\"aaa\\r\"" P.stringLiteral
    (StringLiteralException (InvalidEscapeSequence 'r'))
  handleCustomError "\"aaa\\b\"" P.stringLiteral
    (StringLiteralException (InvalidEscapeSequence 'b'))
  handleCustomError "\"aaa\\t\"" P.stringLiteral
    (StringLiteralException (InvalidEscapeSequence 't'))
  handleCustomError "\"aaa\n\"" P.stringLiteral
    (StringLiteralException (InvalidChar '\n'))
  handleCustomError "\"aaa\r\"" P.stringLiteral
    (StringLiteralException (InvalidChar '\r'))
  handleCustomError "{ TAG 2 (int | string) }" P.codeEntry
    (WrongTagArgs 2 (PositiveUnsafe 2))
  handleCustomError "{ ACCESS 2 2 }" P.codeEntry
    (WrongAccessArgs 2 (PositiveUnsafe 2))
  handleCustomError "{ SET 2 2 }" P.codeEntry
    (WrongSetArgs 2 (PositiveUnsafe 2))
  where
    handleCustomError
      :: HasCallStack => Text -> Parser a -> CustomParserException -> Expectation
    handleCustomError text parser customException =
      case P.parseNoEnv parser "" text of
        Right _ -> expectationFailure "expecting parser to fail"
        Left bundle -> case toList $ bundleErrors bundle of
          [FancyError _ (toList -> [ErrorCustom e])] ->
            e `shouldBe` customException
          _ ->
            expectationFailure $
              "expecting single ErrorCustom, but got " <>
              errorBundlePretty bundle

unit_letType :: Expectation
unit_letType = do
  P.parseNoEnv P.letType "" "type kek = int" `shouldSatisfy` isRight
  -- They used to be prohibited, but now we permit them.
  P.parseNoEnv P.letType "" "type Parameter = int" `shouldSatisfy` isRight
  P.parseNoEnv P.letType "" "type Storage = int" `shouldSatisfy` isRight