packages feed

phino-0.0.107: test/ParserSpec.hs

{-# LANGUAGE OverloadedStrings #-}
-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}

{-# HLINT ignore "Use tuple-section" #-}

module ParserSpec where

import AST
import Control.Monad (forM_)
import Data.Either (isLeft, isRight)
import Data.List (isInfixOf)
import Files (allPathsIn)
import Parser
import System.FilePath (takeBaseName)
import Test.Hspec (Example (Arg), Expectation, Spec, SpecWith, anyException, describe, it, runIO, shouldBe, shouldReturn, shouldSatisfy, shouldThrow)

test ::
  (Eq a, Show a) =>
  (String -> Either String a) ->
  [(String, Maybe a)] ->
  SpecWith (Arg Expectation)
test function useCases =
  forM_ useCases $ \(ipt, res) ->
    it ipt $ case res of
      Just right -> function ipt `shouldBe` Right right
      _ -> function ipt `shouldSatisfy` isLeft

fails ::
  (Show a) =>
  (String -> Either String a) ->
  [(String, String)] ->
  SpecWith (Arg Expectation)
fails function useCases =
  forM_ useCases $ \(ipt, fragment) ->
    it ipt (function ipt `shouldSatisfy` either (isInfixOf fragment) (const False))

spec :: Spec
spec = do
  describe "parse expression" $
    test
      parseExpression
      [ ("[[]]", Just (ExFormation [BiVoid AtRho]))
      , ("T(x -> Q)", Just (ExApplication ExTermination (ArTau (AtLabel "x") ExRoot)))
      , ("Q.org.eolang", Just (ExDispatch (ExDispatch ExRoot (AtLabel "org")) (AtLabel "eolang")))
      , ("[[x -> $, y -> ?]]", Just (ExFormation [BiTau (AtLabel "x") ExXi, BiVoid (AtLabel "y"), BiVoid AtRho]))
      ]

  describe "parse expression" $
    test
      parseExpression
      [ ("Q.!t", Just (ExDispatch ExRoot (AtMeta "t")))
      , ("[[]](!t1 -> $)", Just (ExApplication (ExFormation [BiVoid AtRho]) (ArTau (AtMeta "t1") ExXi)))
      ,
        ( "[[]](~0 -> $)(~11 -> Q)"
        , Just
            ( ExApplication
                ( ExApplication
                    (ExFormation [BiVoid AtRho])
                    (ArAlpha (Alpha 0) ExXi)
                )
                (ArAlpha (Alpha 11) ExRoot)
            )
        )
      , ("[[]](x -> $, y -> Q)", Just (ExApplication (ExApplication (ExFormation [BiVoid AtRho]) (ArTau (AtLabel "x") ExXi)) (ArTau (AtLabel "y") ExRoot)))
      , ("[[!B, !B1]]", Just (ExFormation [BiMeta "B", BiMeta "B1"]))
      , ("[[!B2, !t2 -> $]]", Just (ExFormation [BiMeta "B2", BiTau (AtMeta "t2") ExXi]))
      , ("!e0", Just (ExMeta "e0"))
      , ("!k", Just (ExMeta "k"))
      , ("[[x -> !k1]]", Just (ExFormation [BiTau (AtLabel "x") (ExMeta "k1"), BiVoid AtRho]))
      , ("[[x -> !e]]", Just (ExFormation [BiTau (AtLabel "x") (ExMeta "e"), BiVoid AtRho]))
      , ("[[!t -> !e1]]", Just (ExFormation [BiTau (AtMeta "t") (ExMeta "e1")]))
      , ("[[D> --]]", Just (ExFormation [BiDelta BtEmpty, BiVoid AtRho]))
      , ("[[D> 1F-]]", Just (ExFormation [BiDelta (BtOne "1F"), BiVoid AtRho]))
      , ("[[\n  L> Func,\n  D> 00-\n]]", Just (ExFormation [BiLambda (Function "Func"), BiDelta (BtOne "00"), BiVoid AtRho]))
      , ("[[D> 1F-2A-00]]", Just (ExFormation [BiDelta (BtMany ["1F", "2A", "00"]), BiVoid AtRho]))
      , ("[[D> !d0]]", Just (ExFormation [BiDelta (BtMeta "d0"), BiVoid AtRho]))
      , ("[[L> Function]]", Just (ExFormation [BiLambda (Function "Function"), BiVoid AtRho]))
      , ("[[L> !F3]]", Just (ExFormation [BiLambda (FnMeta "F3"), BiVoid AtRho]))
      , ("[[x() -> [[]] ]]", Just (ExFormation [BiTau (AtLabel "x") (ExFormation [BiVoid AtRho]), BiVoid AtRho]))
      ,
        ( "[[y(^,@,z) -> [[q -> Q.a]] ]]"
        , Just
            ( ExFormation
                [ BiTau
                    (AtLabel "y")
                    ( ExFormation
                        [ BiVoid AtRho
                        , BiVoid AtPhi
                        , BiVoid (AtLabel "z")
                        , BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "a"))
                        ]
                    )
                , BiVoid AtRho
                ]
            )
        )
      ,
        ( "!e(x(^,@) -> [[w -> !e1]])"
        , Just
            ( ExApplication
                (ExMeta "e")
                ( ArTau
                    (AtLabel "x")
                    ( ExFormation
                        [ BiVoid AtRho
                        , BiVoid AtPhi
                        , BiTau (AtLabel "w") (ExMeta "e1")
                        ]
                    )
                )
            )
        )
      ,
        ( "[[x -> y.z, w -> ^, u -> @, p -> !t, q -> !e]]"
        , Just
            ( ExFormation
                [ BiTau
                    (AtLabel "x")
                    (ExDispatch (ExDispatch ExXi (AtLabel "y")) (AtLabel "z"))
                , BiTau
                    (AtLabel "w")
                    (ExDispatch ExXi AtRho)
                , BiTau
                    (AtLabel "u")
                    (ExDispatch ExXi AtPhi)
                , BiTau
                    (AtLabel "p")
                    (ExDispatch ExXi (AtMeta "t"))
                , BiTau
                    (AtLabel "q")
                    (ExMeta "e")
                , BiVoid AtRho
                ]
            )
        )
      ,
        ( "Q.x(y, [[]].z, Q.y(^,@))"
        , Just
            ( ExApplication
                ( ExApplication
                    ( ExApplication
                        (ExDispatch ExRoot (AtLabel "x"))
                        (ArAlpha (Alpha 0) (ExDispatch ExXi (AtLabel "y")))
                    )
                    (ArAlpha (Alpha 1) (ExDispatch (ExFormation [BiVoid AtRho]) (AtLabel "z")))
                )
                ( ArAlpha
                    (Alpha 2)
                    ( ExApplication
                        ( ExApplication
                            (ExDispatch ExRoot (AtLabel "y"))
                            (ArAlpha (Alpha 0) (ExDispatch ExXi AtRho))
                        )
                        (ArAlpha (Alpha 1) (ExDispatch ExXi AtPhi))
                    )
                )
            )
        )
      ,
        ( "5.plus(5.q(\"hello\".length))"
        , Just
            ( ExApplication
                ( ExDispatch
                    (DataNumber (BtMany ["40", "14", "00", "00", "00", "00", "00", "00"]))
                    (AtLabel "plus")
                )
                ( ArAlpha
                    (Alpha 0)
                    ( ExApplication
                        ( ExDispatch
                            (DataNumber (BtMany ["40", "14", "00", "00", "00", "00", "00", "00"]))
                            (AtLabel "q")
                        )
                        ( ArAlpha
                            (Alpha 0)
                            ( ExDispatch
                                (DataString (BtMany ["68", "65", "6C", "6C", "6F"]))
                                (AtLabel "length")
                            )
                        )
                    )
                )
            )
        )
      ,
        ( "[[𝐵1, 𝜏0 -> $, x -> 𝑒]]"
        , Just
            ( ExFormation
                [ BiMeta "B1"
                , BiTau (AtMeta "t0") ExXi
                , BiTau (AtLabel "x") (ExMeta "e")
                ]
            )
        )
      ]

  describe "just parses" $
    forM_
      [ "[[x -> $, y -> ?]]"
      , "[[x() -> [[]] ]]"
      , "Q.x(y() -> [[]])"
      , "Q.x(y(q) -> [[w -> !e]])"
      , "Q.x(~1(^,@) -> [[]])"
      , "Q.x.^.@.!t0"
      , "[[x -> y.z]]"
      , "[[x -> ^, y -> @, z -> !t]]"
      , "Q.x(a.b.c, Q.a(b), [[]])"
      , "Q.x(y, [[]].z, Q.y(^,@))"
      , "[[x -> 5.plus(5), y -> \"hello\", z -> 42.5]]"
      , "[[\n  x -> \"Hi\",\n  y -> 42\n]]"
      , "[[x -> -42, y -> +34]]"
      , "⟦x ↦ Φ.org.eolang(z ↦ ξ.f, φ ↦ ρ, t ↦ φ, first ↦ ⟦ λ ⤍ Function_name, Δ ⤍ 42- ⟧)⟧"
      , "[[x -> 1.00e+3, y -> 2.32e-4]]"
      , "[[ x -> \"\\u0001\\u0001\"]]"
      , "[[ x -> \"\\uD835\\uDF11\"]]"
      , "[[ x ↦ \"This plugin has \\x01\\x01\" ]]"
      , "[[ !tfoo -> !e1Some, !t-BAR -> !e_123someW, !Bhi123 ]]"
      , "[[ !B ]](α𝑖 -> !e)"
      , "[[ 𝜏 -> !e, 𝐵 ]]"
      ]
      (\expr -> it expr (parseExpression expr `shouldSatisfy` isRight))

  describe "prohibits" $
    test
      parseExpression
      ( map
          (\input -> (input, Nothing))
          [ "Q.x()"
          , "[[x(^, @, y) -> [[q -> QQ]] ]]"
          , "Q * !t1 * !t2"
          , "Q(x -> [[]])"
          , "$(x -> [[]])"
          , "Q.x(x -> ?)"
          , "Q.x(L> Func)"
          , "Q.x(D> --)"
          , "Q.x(~1 -> ?)"
          , "Q.x(L> !F)"
          , "Q.x(D> !b)"
          , "[[α0 -> Q.x]]"
          , "[[x(α1) -> [[]] ]]"
          , "[[y(!e) -> [[]] ]]"
          , "[[z(w) -> Q.x]]"
          , "Q.x(y(α1) -> [[]])"
          , "Q.x(1, 2, !B)"
          , "Q.x.α0"
          , "Q.x(~1 -> Q.y, x -> 5, !B1)"
          , "Q.x(𝐵1, 𝜏0 -> $, x -> 𝑒)"
          , "[[ x -> \"\\uD800\"]]"
          , "[[ x -> \"\\uDFFF\"]]"
          , "[[ x -> \"\\uD835\\u0041\"]]"
          , "[[ x -> 1, x -> 2 ]]"
          , "⟦ k ↦ ⟦ λ ⤍ Foo, λ ⤍ Bar ⟧ ⟧"
          , "⟦ k ↦ ⟦ Δ ⤍ 42-, Δ ⤍ 55- ⟧ ⟧"
          ]
      )

  describe "points at the typo instead of the beginning of the binding" $
    fails
      parseExpression
      [ ("[[ D> x ]]", "expression:1:7:")
      , ("[[ L> 42 ]]", "expression:1:7:")
      , ("⟦ a ↦ Φ.foo() ⟧", "expression:1:13:")
      , ("[[ x -> ]]", "expression:1:9:")
      , ("[[ x -> Q.y(] ]]", "expression:1:13:")
      , ("[[ y -> 5, x -> Q.z(} ]]", "expression:1:21:")
      ]

  describe "tells what a binding prefix may be followed by" $
    fails
      parseExpression
      [ ("[[ D> x ]]", "expecting bytes")
      , ("[[ L> 42 ]]", "function name")
      , ("[[ x -> ]]", "expecting '?', '∅', or expression head")
      ]

  describe "parse packs" $ do
    packs <- runIO (allPathsIn "test-resources/parser-packs")
    forM_
      packs
      ( \pack -> do
          content <- runIO (readFile pack)
          it (takeBaseName pack) (parseExpression content `shouldSatisfy` isRight)
      )

  describe "process typo packs" $ do
    packs <- runIO (allPathsIn "test-resources/phi-typos-packs")
    forM_
      packs
      ( \pack -> do
          content <- runIO (readFile pack)
          it (takeBaseName pack) (parseExpression content `shouldSatisfy` isLeft)
      )

  describe "parse bytes" $
    test
      parseBytes
      [ ("--", Just BtEmpty)
      , ("00-", Just (BtOne "00"))
      , ("FF-", Just (BtOne "FF"))
      , ("AB-", Just (BtOne "AB"))
      , ("1F-2A-00", Just (BtMany ["1F", "2A", "00"]))
      , ("01-02-03-04-05", Just (BtMany ["01", "02", "03", "04", "05"]))
      , ("!d", Just (BtMeta "d"))
      , ("!d0", Just (BtMeta "d0"))
      , ("!d_test", Just (BtMeta "d_test"))
      , ("δ", Just (BtMeta "d"))
      , ("δ0", Just (BtMeta "d0"))
      , ("GG-", Nothing)
      , ("0-", Nothing)
      , ("000-", Nothing)
      , ("zz-", Nothing)
      ]

  describe "parse binding" $
    test
      parseBinding
      [ ("x -> $", Just (BiTau (AtLabel "x") ExXi))
      , ("y -> Q", Just (BiTau (AtLabel "y") ExRoot))
      , ("z -> ?", Just (BiVoid (AtLabel "z")))
      , ("w -> ∅", Just (BiVoid (AtLabel "w")))
      , ("^ -> T", Just (BiTau AtRho ExTermination))
      , ("@ -> $", Just (BiTau AtPhi ExXi))
      , ("ρ -> Q", Just (BiTau AtRho ExRoot))
      , ("φ -> T", Just (BiTau AtPhi ExTermination))
      , ("!t -> $", Just (BiTau (AtMeta "t") ExXi))
      , ("!t0 -> Q", Just (BiTau (AtMeta "t0") ExRoot))
      , ("D> --", Just (BiDelta BtEmpty))
      , ("D> 42-", Just (BiDelta (BtOne "42")))
      , ("D> 01-02-03", Just (BiDelta (BtMany ["01", "02", "03"])))
      , ("D> !d", Just (BiDelta (BtMeta "d")))
      , ("Δ ⤍ FF-", Just (BiDelta (BtOne "FF")))
      , ("Δ ⤍ --", Just (BiDelta BtEmpty))
      , ("L> Func", Just (BiLambda (Function "Func")))
      , ("L> Function_name", Just (BiLambda (Function "Function_name")))
      , ("L> Aφ", Just (BiLambda (Function "Aφ")))
      , ("λ ⤍ Test", Just (BiLambda (Function "Test")))
      , ("L> !F", Just (BiLambda (FnMeta "F")))
      , ("L> !F0", Just (BiLambda (FnMeta "F0")))
      , ("λ ⤍ 𝑓", Just (BiLambda (FnMeta "F")))
      , ("L> 𝑓2", Just (BiLambda (FnMeta "F2")))
      , ("!B", Just (BiMeta "B"))
      , ("!B0", Just (BiMeta "B0"))
      , ("!B_test", Just (BiMeta "B_test"))
      , ("𝐵", Just (BiMeta "B"))
      , ("𝐵1", Just (BiMeta "B1"))
      , ("x() -> [[]]", Just (BiTau (AtLabel "x") (ExFormation [BiVoid AtRho])))
      , ("y(^) -> [[]]", Just (BiTau (AtLabel "y") (ExFormation [BiVoid AtRho])))
      , ("z(^, @) -> [[]]", Just (BiTau (AtLabel "z") (ExFormation [BiVoid AtRho, BiVoid AtPhi])))
      , ("x -> [[y -> $]]", Just (BiTau (AtLabel "x") (ExFormation [BiTau (AtLabel "y") ExXi, BiVoid AtRho])))
      , ("x ↦ ξ", Just (BiTau (AtLabel "x") ExXi))
      , ("x -> ", Nothing)
      , ("-> Q", Nothing)
      , ("L>", Nothing)
      , ("D>", Nothing)
      ]

  describe "parse attribute" $
    test
      parseAttribute
      [ ("x", Just (AtLabel "x"))
      , ("foo", Just (AtLabel "foo"))
      , ("camelCase", Just (AtLabel "camelCase"))
      , ("with_underscore", Just (AtLabel "with_underscore"))
      , ("with-dash", Just (AtLabel "with-dash"))
      , ("^", Just AtRho)
      , ("ρ", Just AtRho)
      , ("@", Just AtPhi)
      , ("φ", Just AtPhi)
      , ("!t", Just (AtMeta "t"))
      , ("!t0", Just (AtMeta "t0"))
      , ("!t_test", Just (AtMeta "t_test"))
      , ("𝜏", Just (AtMeta "t"))
      , ("𝜏0", Just (AtMeta "t0"))
      , ("a0", Just (AtLabel "a0"))
      , ("a1", Just (AtLabel "a1"))
      , ("a123", Just (AtLabel "a123"))
      , ("α0", Nothing)
      , ("α42", Nothing)
      , ("~0", Nothing)
      , ("~1", Nothing)
      , ("~123", Nothing)
      , ("X", Nothing)
      , ("123", Nothing)
      , ("", Nothing)
      ]

  describe "parse number" $
    test
      parseNumber
      [ ("0", Just (DataNumber (BtMany ["00", "00", "00", "00", "00", "00", "00", "00"])))
      , ("-0", Just (DataNumber (BtMany ["80", "00", "00", "00", "00", "00", "00", "00"])))
      , ("-0.0", Just (DataNumber (BtMany ["80", "00", "00", "00", "00", "00", "00", "00"])))
      , ("+0", Just (DataNumber (BtMany ["00", "00", "00", "00", "00", "00", "00", "00"])))
      , ("1", Just (DataNumber (BtMany ["3F", "F0", "00", "00", "00", "00", "00", "00"])))
      , ("-1", Just (DataNumber (BtMany ["BF", "F0", "00", "00", "00", "00", "00", "00"])))
      , ("+1", Just (DataNumber (BtMany ["3F", "F0", "00", "00", "00", "00", "00", "00"])))
      , ("42", Just (DataNumber (BtMany ["40", "45", "00", "00", "00", "00", "00", "00"])))
      , ("-42", Just (DataNumber (BtMany ["C0", "45", "00", "00", "00", "00", "00", "00"])))
      , ("3.14", Just (DataNumber (BtMany ["40", "09", "1E", "B8", "51", "EB", "85", "1F"])))
      , ("1.5", Just (DataNumber (BtMany ["3F", "F8", "00", "00", "00", "00", "00", "00"])))
      , ("-0.5", Just (DataNumber (BtMany ["BF", "E0", "00", "00", "00", "00", "00", "00"])))
      , ("1e3", Just (DataNumber (BtMany ["40", "8F", "40", "00", "00", "00", "00", "00"])))
      , ("1E3", Just (DataNumber (BtMany ["40", "8F", "40", "00", "00", "00", "00", "00"])))
      , ("1.5e2", Just (DataNumber (BtMany ["40", "62", "C0", "00", "00", "00", "00", "00"])))
      , ("2e-3", Just (DataNumber (BtMany ["3F", "60", "62", "4D", "D2", "F1", "A9", "FC"])))
      , ("-1e10", Just (DataNumber (BtMany ["C2", "02", "A0", "5F", "20", "00", "00", "00"])))
      , ("abc", Nothing)
      , ("", Nothing)
      ]

  describe "parseExpressionThrows" $ do
    it "returns expression on valid input" $
      parseExpressionThrows "T" `shouldReturn` ExTermination
    it "throws on invalid input" $
      parseExpressionThrows "invalid expression ]][[" `shouldThrow` anyException

  describe "parseExpressionThrows" $ do
    it "returns expression on valid input" $
      parseExpressionThrows "Q.x" `shouldReturn` ExDispatch ExRoot (AtLabel "x")
    it "throws on invalid input" $
      parseExpressionThrows "[[invalid" `shouldThrow` anyException

  describe "parseAttributeThrows" $ do
    it "returns attribute on valid input" $
      parseAttributeThrows "foo" `shouldReturn` AtLabel "foo"
    it "throws on invalid input" $
      parseAttributeThrows "123invalid" `shouldThrow` anyException

  describe "parseNumberThrows" $ do
    it "returns number on valid input" $ do
      result <- parseNumberThrows "42"
      case result of
        DataNumber _ -> return ()
        _ -> fail "expected DataNumber"
    it "throws on invalid input" $
      parseNumberThrows "notanumber" `shouldThrow` anyException

  describe "parse string escapes" $
    test
      parseExpression
      [ ("\"hello\"", Just (DataString (BtMany ["68", "65", "6C", "6C", "6F"])))
      , ("\"\"", Just (DataString BtEmpty))
      , ("\"a\"", Just (DataString (BtOne "61")))
      , ("\"\\n\"", Just (DataString (BtOne "0A")))
      , ("\"\\r\"", Just (DataString (BtOne "0D")))
      , ("\"\\t\"", Just (DataString (BtOne "09")))
      , ("\"\\\\\"", Just (DataString (BtOne "5C")))
      , ("\"\\\"\"", Just (DataString (BtOne "22")))
      , ("\"\\b\"", Just (DataString (BtOne "08")))
      , ("\"\\f\"", Just (DataString (BtOne "0C")))
      , ("\"\\x41\"", Just (DataString (BtOne "41")))
      , ("\"\\x00\"", Just (DataString (BtOne "00")))
      , ("\"\\u0041\"", Just (DataString (BtOne "41")))
      , ("\"\\u0000\"", Just (DataString (BtOne "00")))
      , ("\"line1\\nline2\"", Just (DataString (BtMany ["6C", "69", "6E", "65", "31", "0A", "6C", "69", "6E", "65", "32"])))
      ]

  describe "parse unicode syntax" $
    test
      parseExpression
      [ ("ξ", Just ExXi)
      , ("Φ", Just ExRoot)
      , ("⊥", Just ExTermination)
      , ("⟦⟧", Just (ExFormation [BiVoid AtRho]))
      , ("⟦ x ↦ ξ ⟧", Just (ExFormation [BiTau (AtLabel "x") ExXi, BiVoid AtRho]))
      , ("ξ.ρ", Just (ExDispatch ExXi AtRho))
      , ("ξ.φ", Just (ExDispatch ExXi AtPhi))
      ]

  describe "parse labels with special characters" $
    test
      parseExpression
      [ ("foo123", Just (ExDispatch ExXi (AtLabel "foo123")))
      , ("with-dash", Just (ExDispatch ExXi (AtLabel "with-dash")))
      , ("with_underscore", Just (ExDispatch ExXi (AtLabel "with_underscore")))
      , ("aкирилиця", Just (ExDispatch ExXi (AtLabel "aкирилиця")))
      , ("a日本語", Just (ExDispatch ExXi (AtLabel "a日本語")))
      , ("name123_test", Just (ExDispatch ExXi (AtLabel "name123_test")))
      ]

  describe "parse complex formations" $
    test
      parseExpression
      [ ("[[^ -> ?]]", Just (ExFormation [BiVoid AtRho]))
      , ("[[@ -> ?]]", Just (ExFormation [BiVoid AtPhi, BiVoid AtRho]))
      , ("[[^ -> ?, @ -> ?]]", Just (ExFormation [BiVoid AtRho, BiVoid AtPhi]))
      , ("[[^ -> Q, @ -> $]]", Just (ExFormation [BiTau AtRho ExRoot, BiTau AtPhi ExXi]))
      ]

  describe "parse applications with mixed bindings" $
    test
      parseExpression
      [ ("[[]](Q)", Just (ExApplication (ExFormation [BiVoid AtRho]) (ArAlpha (Alpha 0) ExRoot)))
      , ("[[]](Q, T)", Just (ExApplication (ExApplication (ExFormation [BiVoid AtRho]) (ArAlpha (Alpha 0) ExRoot)) (ArAlpha (Alpha 1) ExTermination)))
      , ("Q.x(y -> $)", Just (ExApplication (ExDispatch ExRoot (AtLabel "x")) (ArTau (AtLabel "y") ExXi)))
      , ("[[x -> ?]].x(Q)", Just (ExApplication (ExDispatch (ExFormation [BiVoid (AtLabel "x"), BiVoid AtRho]) (AtLabel "x")) (ArAlpha (Alpha 0) ExRoot)))
      , ("[[]](~!i -> $)", Just (ExApplication (ExFormation [BiVoid AtRho]) (ArAlpha (AlMeta "i") ExXi)))
      , ("[[]](α𝑖 -> Q)", Just (ExApplication (ExFormation [BiVoid AtRho]) (ArAlpha (AlMeta "i") ExRoot)))
      , ("Q.foo(a1 -> Q.y)", Just (ExApplication (ExDispatch ExRoot (AtLabel "foo")) (ArTau (AtLabel "a1") (ExDispatch ExRoot (AtLabel "y"))))) -- #875: "a"-prefixed label in argument position is a named binding, not a positional alpha
      ]

  describe "parse meta expressions" $
    test
      parseExpression
      [ ("!e", Just (ExMeta "e"))
      , ("!e0", Just (ExMeta "e0"))
      , ("!e_test", Just (ExMeta "e_test"))
      , ("𝑒", Just (ExMeta "e"))
      , ("𝑒0", Just (ExMeta "e0"))
      , ("!e.x", Just (ExDispatch (ExMeta "e") (AtLabel "x")))
      , ("!e(Q)", Just (ExApplication (ExMeta "e") (ArAlpha (Alpha 0) ExRoot)))
      , ("!n", Just (ExMeta "n"))
      , ("!n1", Just (ExMeta "n1"))
      , ("𝑛", Just (ExMeta "n"))
      , ("𝑛1", Just (ExMeta "n1"))
      , ("𝑛.x", Just (ExDispatch (ExMeta "n") (AtLabel "x")))
      ]

  describe "parse whitespace handling" $
    forM_
      [ "[[  x   ->   Q  ]]"
      , "[[\n\tx\n\t->\n\tQ\n\t]]"
      , "  Q  .  x  "
      , "Q.x(  y  ->  $  )"
      , "  [[  x  ->  Q  ]]  "
      ]
      (\expr -> it expr (parseExpression expr `shouldSatisfy` isRight))