packages feed

burrito-1.1.0.0: src/test/Main.hs

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

module Main
  ( main
  )
where

import qualified Burrito
import qualified Burrito.Internal.Parse as Parse
import qualified Burrito.Internal.Render as Render
import qualified Burrito.Internal.Type.Case as Case
import qualified Burrito.Internal.Type.Character as Character
import qualified Burrito.Internal.Type.Digit as Digit
import qualified Burrito.Internal.Type.Expression as Expression
import qualified Burrito.Internal.Type.Field as Field
import qualified Burrito.Internal.Type.Literal as Literal
import qualified Burrito.Internal.Type.MaxLength as MaxLength
import qualified Burrito.Internal.Type.Modifier as Modifier
import qualified Burrito.Internal.Type.Name as Name
import qualified Burrito.Internal.Type.Operator as Operator
import qualified Burrito.Internal.Type.Template as Template
import qualified Burrito.Internal.Type.Token as Token
import qualified Burrito.Internal.Type.Value as Value
import qualified Burrito.Internal.Type.Variable as Variable
import qualified Control.Monad as Monad
import qualified Data.List as List
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import qualified Data.Set as Set
import qualified Data.String as String
import qualified Data.Text as Text
import qualified GHC.Stack as Stack
import qualified Test.Hspec as Hspec
import qualified Test.Hspec.QuickCheck as Hspec
import qualified Test.QuickCheck as QC

main :: IO ()
main = Hspec.hspec . Hspec.describe "Burrito" $ do

  Monad.forM_ tests $ \test ->
    Hspec.it (show (testInput test, unwrapOutput $ testOutput test))
      $ runTest test

  Hspec.describe "match" $ do
    let
      matchTest
        :: Stack.HasCallStack
        => String
        -> [(String, Burrito.Value)]
        -> String
        -> Hspec.Spec
      matchTest input values output = Hspec.it (show (input, output)) $ do
        template <- maybe (fail "invalid Template") pure $ Burrito.parse input
        let matches = Burrito.match output template
        matches `Hspec.shouldSatisfy` elem values
        Monad.forM_ matches $ \match -> do
          let it = Burrito.expand match template
          Monad.when (it /= output) . fail $ show
            (input, values, output, matches, match, it)

    matchTest "" [] ""
    matchTest "a" [] "a"
    matchTest "!" [] "!"
    matchTest "\xa0" [] "%C2%A0"

    matchTest "{a}" ["a" =: s ""] ""
    matchTest "{a}" ["a" =: s "A"] "A"
    matchTest "{a}" ["a" =: s "AB"] "AB"

    matchTest "a{b}" ["b" =: s ""] "a"
    matchTest "a{b}" ["b" =: s "B"] "aB"
    matchTest "a{b}" ["b" =: s "BC"] "aBC"

    matchTest "{a}b" ["a" =: s ""] "b"
    matchTest "{a}b" ["a" =: s "A"] "Ab"
    matchTest "{a}b" ["a" =: s "AB"] "ABb"

    matchTest "{a}{a}" ["a" =: s "A"] "AA"
    matchTest "{a}{a}" ["a" =: s "AB"] "ABAB"

    matchTest "{a}" ["a" =: s "%"] "%25"
    matchTest "{a}" ["a" =: s "/"] "%2F"
    matchTest "{a}" ["a" =: s "\xa0"] "%C2%A0"
    matchTest "{a}" ["a" =: s "\xd7ff"] "%ED%9F%BF"
    matchTest "{a}" ["a" =: s "\x10000"] "%F0%90%80%80"
    matchTest "{a}" ["a" =: s "A/B"] "A%2FB"
    matchTest "{a}" ["a" =: s "WX\xa0\xa1YZ"] "WX%C2%A0%C2%A1YZ"

    matchTest "{+a}" ["a" =: s "%"] "%25"
    matchTest "{+a}" ["a" =: s "/"] "/"

    matchTest "{#a}" [] ""
    matchTest "{#a}" ["a" =: s ""] "#"
    matchTest "{#a}" ["a" =: s "A"] "#A"
    matchTest "{#a}" ["a" =: s "%"] "#%25"
    matchTest "{#a}" ["a" =: s "/"] "#/"

    matchTest "{.a}" [] ""
    matchTest "{.a}" ["a" =: s ""] "."
    matchTest "{.a}" ["a" =: s "A"] ".A"
    matchTest "{.a}" ["a" =: s "%"] ".%25"
    matchTest "{.a}" ["a" =: s "/"] ".%2F"

    matchTest "{/a}" [] ""
    matchTest "{/a}" ["a" =: s ""] "/"
    matchTest "{/a}" ["a" =: s "A"] "/A"
    matchTest "{/a}" ["a" =: s "%"] "/%25"
    matchTest "{/a}" ["a" =: s "/"] "/%2F"

    matchTest "{;a}" [] ""
    matchTest "{;a}" ["a" =: s ""] ";a"
    matchTest "{;a}" ["a" =: s "A"] ";a=A"
    matchTest "{;a}" ["a" =: s "%"] ";a=%25"
    matchTest "{;a}" ["a" =: s "/"] ";a=%2F"

    matchTest "{?a}" [] ""
    matchTest "{?a}" ["a" =: s ""] "?a="
    matchTest "{?a}" ["a" =: s "A"] "?a=A"
    matchTest "{?a}" ["a" =: s "%"] "?a=%25"
    matchTest "{?a}" ["a" =: s "/"] "?a=%2F"

    matchTest "{&a}" [] ""
    matchTest "{&a}" ["a" =: s ""] "&a="
    matchTest "{&a}" ["a" =: s "A"] "&a=A"
    matchTest "{&a}" ["a" =: s "%"] "&a=%25"
    matchTest "{&a}" ["a" =: s "/"] "&a=%2F"

    matchTest "{a,b}" ["a" =: s "A", "b" =: s "B"] "A,B"
    matchTest "{+a,b}" ["a" =: s "A", "b" =: s "B"] "A,B"
    matchTest "{#a,b}" ["a" =: s "A", "b" =: s "B"] "#A,B"
    matchTest "{.a,b}" ["a" =: s "A", "b" =: s "B"] ".A.B"
    matchTest "{/a,b}" ["a" =: s "A", "b" =: s "B"] "/A/B"
    matchTest "{;a,b}" ["a" =: s "A", "b" =: s "B"] ";a=A;b=B"
    matchTest "{?a,b}" ["a" =: s "A", "b" =: s "B"] "?a=A&b=B"
    matchTest "{&a,b}" ["a" =: s "A", "b" =: s "B"] "&a=A&b=B"

    matchTest "{a,b}" ["a" =: s "A"] "A"
    matchTest "{a,b,c}" ["a" =: s "A"] "A"
    matchTest "{a,b,c}" ["a" =: s "A", "b" =: s "B"] "A,B"
    matchTest "{a}{a,b}" ["b" =: s "B"] "B"
    matchTest "{a}{a,b,c}" ["b" =: s "B"] "B"
    matchTest "{a,b}{a,b,c}" ["c" =: s "C"] "C"
    matchTest "{b}{a,b,c}" ["a" =: s "A", "c" =: s "C"] "A,C"
    matchTest "{a}{a,b,c}" ["b" =: s "B", "c" =: s "C"] "B,C"

    matchTest "{a:1}/{a}" ["a" =: s "AB"] "A/AB"

    -- TODO: Test matching on explode modifier.
    -- matchTest "{a*}" ["a" =: s "A"] "A"

    -- TODO: Test matching on lists.
    -- matchTest "{a}" ["a" =: l ["A", "B"]] "A,B"

    -- TODO: Test matching on dictionaries.
    -- matchTest "{a}" ["a" =: d ["k" =: "v"]] "k,v"

  Hspec.describe "uriTemplate"
    . Hspec.it "works as an expression"
    $ Just [Burrito.uriTemplate|a{b}c|]
    `Hspec.shouldBe` Burrito.parse "a{b}c"

  Hspec.modifyMaxSize (const 10)
    . Hspec.it "round trips"
    . QC.property
    $ \(Template template) ->
        Burrito.parse (Burrito.render template) == Just template

-- brittany-next-binding --columns 160
tests :: [Test]
tests = mconcat
  [ [ Test "" [] ""
    , Test "" ["a" =: s ""] ""
    , Test "" ["a" =: l []] ""
    , Test "" ["a" =: d []] ""
    , Test "!" [] "!"
    , Test "#" [] "#"
    , Test "$" [] "$"
    , Test "&" [] "&"
    , Test "(" [] "("
    , Test ")" [] ")"
    , Test "*" [] "*"
    , Test "+" [] "+"
    , Test "," [] ","
    , Test "-" [] "-"
    , Test "." [] "."
    , Test "/" [] "/"
    , Test "0" [] "0"
    , Test "9" [] "9"
    , Test ":" [] ":"
    , Test ";" [] ";"
    , Test "=" [] "="
    , Test "?" [] "?"
    , Test "@" [] "@"
    , Test "A" [] "A"
    , Test "Z" [] "Z"
    , Test "[" [] "["
    , Test "]" [] "]"
    , Test "_" [] "_"
    , Test "a" [] "a"
    , Test "z" [] "z"
    , Test "~" [] "~"
    , Test "\xa0" [] "%C2%A0"
    , Test "\xd7ff" [] "%ED%9F%BF"
    , Test "\xf900" [] "%EF%A4%80"
    , Test "\xfdcf" [] "%EF%B7%8F"
    , Test "\xfdf0" [] "%EF%B7%B0"
    , Test "\xffef" [] "%EF%BF%AF"
    , Test "\x10000" [] "%F0%90%80%80"
    , Test "\x1fffd" [] "%F0%9F%BF%BD"
    , Test "\x20000" [] "%F0%A0%80%80"
    , Test "\x2fffd" [] "%F0%AF%BF%BD"
    , Test "\x30000" [] "%F0%B0%80%80"
    , Test "\x3fffd" [] "%F0%BF%BF%BD"
    , Test "\x40000" [] "%F1%80%80%80"
    , Test "\x4fffd" [] "%F1%8F%BF%BD"
    , Test "\x50000" [] "%F1%90%80%80"
    , Test "\x5fffd" [] "%F1%9F%BF%BD"
    , Test "\x60000" [] "%F1%A0%80%80"
    , Test "\x6fffd" [] "%F1%AF%BF%BD"
    , Test "\x70000" [] "%F1%B0%80%80"
    , Test "\x7fffd" [] "%F1%BF%BF%BD"
    , Test "\x80000" [] "%F2%80%80%80"
    , Test "\x8fffd" [] "%F2%8F%BF%BD"
    , Test "\x90000" [] "%F2%90%80%80"
    , Test "\x9fffd" [] "%F2%9F%BF%BD"
    , Test "\xa0000" [] "%F2%A0%80%80"
    , Test "\xafffd" [] "%F2%AF%BF%BD"
    , Test "\xb0000" [] "%F2%B0%80%80"
    , Test "\xbfffd" [] "%F2%BF%BF%BD"
    , Test "\xc0000" [] "%F3%80%80%80"
    , Test "\xcfffd" [] "%F3%8F%BF%BD"
    , Test "\xd0000" [] "%F3%90%80%80"
    , Test "\xdfffd" [] "%F3%9F%BF%BD"
    , Test "\xe1000" [] "%F3%A1%80%80"
    , Test "\xefffd" [] "%F3%AF%BF%BD"
    , Test "\xe000" [] "%EE%80%80"
    , Test "\xf8ff" [] "%EF%A3%BF"
    , Test "\xf0000" [] "%F3%B0%80%80"
    , Test "\xffffd" [] "%F3%BF%BF%BD"
    , Test "\x100000" [] "%F4%80%80%80"
    , Test "\x10fffd" [] "%F4%8F%BF%BD"
    , Test "%00" [] "%00"
    , Test "%AA" [] "%AA"
    , Test "%Aa" [] "%Aa"
    , Test "%aA" [] "%aA"
    , Test "%aa" [] "%aa"
    , Test "%" [] noParse
    , Test "%0" [] noParse
    , Test "%0z" [] noParse
    , Test "%z" [] noParse
    , Test "%30" [] "%30"
    , Test " " [] noParse
    , Test "\"" [] noParse
    , Test "'" [] noParse
    , Test "%" [] noParse
    , Test "<" [] noParse
    , Test ">" [] noParse
    , Test "\\" [] noParse
    , Test "^" [] noParse
    , Test "`" [] noParse
    , Test "{" [] noParse
    , Test "|" [] noParse
    , Test "}" [] noParse
    , Test "{}" [] noParse
    , Test "{,}" [] noParse
    , Test "{a,,b}" [] noParse
    , Test "{+}" [] noParse
    , Test "{:1}" [] noParse
    , Test "{*}" [] noParse
    , Test "{AZ}" [] ""
    , Test "{az}" [] ""
    , Test "{09}" [] ""
    , Test "{_a}" [] ""
    , Test "{a_}" [] ""
    , Test "{_}" [] ""
    , Test "{A.A}" [] ""
    , Test "{a.a}" [] ""
    , Test "{0.0}" [] ""
    , Test "{_._}" [] ""
    , Test "{%aa.%aa}" [] ""
    , Test "{.}" [] noParse
    , Test "{a.}" [] noParse
    , Test "{+.a}" [] noParse
    , Test "{a..b}" [] noParse
    , Test "{%00}" [] ""
    , Test "{%}" [] noParse
    , Test "{%0}" [] noParse
    , Test "{%0z}" [] noParse
    , Test "{%z}" [] noParse
    , Test "{!}" [] noParse
    , Test "{" [] noParse
    , Test "{{}" [] noParse
    , Test "}" [] noParse
    , Test "{}}" [] noParse
    , Test "{a,b}" [] ""
    , Test "{a,b,c,d}" [] ""
    , Test "{a,a}" [] ""
    , Test "{a:5}" [] ""
    , Test "{a:67}" [] ""
    , Test "{a:801}" [] ""
    , Test "{a:234}" [] ""
    , Test "{a:9999}" [] ""
    , Test "{a:123}" ["a" =: s (replicate 200 'a')] . Output . Just $ replicate 123 'a'
    , Test "{a:}" [] noParse
    , Test "{a:0}" [] noParse
    , Test "{a:10000}" [] noParse
    , Test "{a:-1}" [] noParse
    , Test "{a*}" [] ""
    , Test "{a:1*}" [] noParse
    , Test "{a*:1}" [] noParse
    , Test "{a,b:1,c*}" [] ""
    , Test "{+a}" [] ""
    , Test "{#a}" [] ""
    , Test "{.a}" [] ""
    , Test "{/a}" [] ""
    , Test "{;a}" [] ""
    , Test "{?a}" [] ""
    , Test "{&a}" [] ""
    , Test "{=a}" [] noParse
    , Test "{,a}" [] noParse
    , Test "{!a}" [] noParse
    , Test "{@a}" [] noParse
    , Test "{|a}" [] noParse
    , Test "{+#a}" [] noParse
    , Test "{+a,#b}" [] noParse
    , Test "{+a:1}" [] ""
    , Test "{#a*}" [] ""
    , Test "{+a,b}" [] ""
    , Test "{#a,b}" [] ""
    , Test "{.a,b}" [] ""
    , Test "{/a,b}" [] ""
    , Test "{;a,b}" [] ""
    , Test "{?a,b}" [] ""
    , Test "{&a,b}" [] ""
    , Test "{a}{b}" [] ""
    , Test "{a}{b}{c}{d}" [] ""
    , Test "{a}{a}" [] ""
    , Test "{{}}" [] noParse
    , Test "{a{b}}" [] noParse
    , Test "{{a}b}" [] noParse
    , Test "{a{b}c}" [] noParse
    , Test "a{b}" [] "a"
    , Test "{a}b" [] "b"
    , Test "a{b}c" [] "ac"
    , Test "{a}b{c}" [] "b"
    , Test "{a}" [] ""
    , Test "{+a}" [] ""
    , Test "{#a}" [] ""
    , Test "{.a}" [] ""
    , Test "{/a}" [] ""
    , Test "{;a}" [] ""
    , Test "{?a}" [] ""
    , Test "{&a}" [] ""
    , Test "http://example.com/~{username}/" ["username" =: s "fred"] "http://example.com/~fred/"
    , Test "http://example.com/~{username}/" ["username" =: s "mark"] "http://example.com/~mark/"
    , Test "http://example.com/dictionary/{term:1}/{term}" ["term" =: s "cat"] "http://example.com/dictionary/c/cat"
    , Test "http://example.com/dictionary/{term:1}/{term}" ["term" =: s "dog"] "http://example.com/dictionary/d/dog"
    , Test "http://example.com/search{?q,lang}" ["q" =: s "cat", "lang" =: s "en"] "http://example.com/search?q=cat&lang=en"
    , Test "http://example.com/search{?q,lang}" ["q" =: s "chien", "lang" =: s "fr"] "http://example.com/search?q=chien&lang=fr"
    , Test "http://www.example.com/foo{?query,number}" ["query" =: s "mycelium", "number" =: s "100"] "http://www.example.com/foo?query=mycelium&number=100"
    , Test "http://www.example.com/foo{?query,number}" ["number" =: s "100"] "http://www.example.com/foo?number=100"
    , Test "http://www.example.com/foo{?query,number}" [] "http://www.example.com/foo"
    , Test "{a}" [] ""
    , Test "{a}" ["a" =: l []] ""
    , Test "{a}" ["a" =: d []] ""
    , Test "{a}" ["a" =: s ""] ""
    , Test "{a}" ["a" =: s "A"] "A"
    , Test "{a}" ["a" =: s "~"] "~"
    , Test "{a}" ["a" =: s "%"] "%25"
    , Test "{a}" ["a" =: s "?"] "%3F"
    , Test "{a}" ["a" =: s "&"] "%26"
    , Test "{a}" ["a" =: s "\xa0"] "%C2%A0"
    , Test "{a}" ["a" =: s "\xd7ff"] "%ED%9F%BF"
    , Test "{a}" ["a" =: s "\x10000"] "%F0%90%80%80"
    , Test "{a}" ["a" =: l ["A"]] "A"
    , Test "{a}" ["a" =: l ["A", "B"]] "A,B"
    , Test "{a}" ["a" =: l ["%"]] "%25"
    , Test "{a}" ["a" =: l ["\xa0"]] "%C2%A0"
    , Test "{a}" ["a" =: d ["A" =: "1"]] "A,1"
    , Test "{a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"
    , Test "{a}" ["a" =: d ["A" =: "%"]] "A,%25"
    , Test "{a}" ["a" =: d ["A" =: "\xa0"]] "A,%C2%A0"
    , Test "{a}" ["a" =: d ["%" =: "1"]] "%25,1"
    , Test "{a*}" [] ""
    , Test "{a*}" ["a" =: s ""] ""
    , Test "{a*}" ["a" =: s "A"] "A"
    , Test "{a*}" ["a" =: l []] ""
    , Test "{a*}" ["a" =: l ["A"]] "A"
    , Test "{a*}" ["a" =: l ["A", "B"]] "A,B"
    , Test "{a*}" ["a" =: d []] ""
    , Test "{a*}" ["a" =: d ["A" =: "1"]] "A=1"
    , Test "{a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"
    , Test "{a:1}" [] ""
    , Test "{a:1}" ["a" =: s ""] ""
    , Test "{a:1}" ["a" =: s "A"] "A"
    , Test "{a:1}" ["a" =: s "AB"] "A"
    , Test "{a:1}" ["a" =: s "%B"] "%25"
    , Test "{a:1}" ["a" =: s "\xa0\&B"] "%C2%A0"
    , Test "{a:1}" ["a" =: s "\xd7ff\&B"] "%ED%9F%BF"
    , Test "{a:1}" ["a" =: s "\x10000\&B"] "%F0%90%80%80"
    , Test "{a:1}" ["a" =: l []] ""
    , Test "{a:1}" ["a" =: l ["AB"]] "AB"
    , Test "{a:1}" ["a" =: l ["AB", "CD"]] "AB,CD"
    , Test "{a:1}" ["a" =: d []] ""
    , Test "{a:1}" ["a" =: d ["AB" =: "12"]] "AB,12"
    , Test "{a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "AB,12,CD,34"
    , Test "{a,a}" [] ""
    , Test "{a,a}" ["a" =: l []] ""
    , Test "{a,a}" ["a" =: d []] ""
    , Test "{a,a}" ["a" =: s ""] ","
    , Test "{a,b}" ["a" =: s ""] ""
    , Test "{a,b}" ["b" =: s ""] ""
    , Test "{%aa}" ["%aa" =: s "A"] "A"
    , Test "{%aa}" ["%aa" =: l ["A", "B"]] "A,B"
    , Test "{%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"
    , Test "{%aa*}" ["%aa" =: s "A"] "A"
    , Test "{%aa*}" ["%aa" =: l ["A", "B"]] "A,B"
    , Test "{%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"
    , Test "{+a}" [] ""
    , Test "{+a}" ["a" =: l []] ""
    , Test "{+a}" ["a" =: d []] ""
    , Test "{+a}" ["a" =: s ""] ""
    , Test "{+a}" ["a" =: s "A"] "A"
    , Test "{+a}" ["a" =: s "~"] "~"
    , Test "{+a}" ["a" =: s "%"] "%25"
    , Test "{+a}" ["a" =: s "?"] "?"
    , Test "{+a}" ["a" =: s "&"] "&"
    , Test "{+a}" ["a" =: s "\xa0"] "%C2%A0"
    , Test "{+a}" ["a" =: s "\xd7ff"] "%ED%9F%BF"
    , Test "{+a}" ["a" =: s "\x10000"] "%F0%90%80%80"
    , Test "{+a}" ["a" =: l ["A"]] "A"
    , Test "{+a}" ["a" =: l ["A", "B"]] "A,B"
    , Test "{+a}" ["a" =: l ["%"]] "%25"
    , Test "{+a}" ["a" =: l ["\xa0"]] "%C2%A0"
    , Test "{+a}" ["a" =: d ["A" =: "1"]] "A,1"
    , Test "{+a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"
    , Test "{+a}" ["a" =: d ["A" =: "%"]] "A,%25"
    , Test "{+a}" ["a" =: d ["A" =: "\xa0"]] "A,%C2%A0"
    , Test "{+a}" ["a" =: d ["%" =: "1"]] "%25,1"
    , Test "{+a*}" [] ""
    , Test "{+a*}" ["a" =: s ""] ""
    , Test "{+a*}" ["a" =: s "A"] "A"
    , Test "{+a*}" ["a" =: l []] ""
    , Test "{+a*}" ["a" =: l ["A"]] "A"
    , Test "{+a*}" ["a" =: l ["A", "B"]] "A,B"
    , Test "{+a*}" ["a" =: d []] ""
    , Test "{+a*}" ["a" =: d ["A" =: "1"]] "A=1"
    , Test "{+a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"
    , Test "{+a:1}" [] ""
    , Test "{+a:1}" ["a" =: s ""] ""
    , Test "{+a:1}" ["a" =: s "A"] "A"
    , Test "{+a:1}" ["a" =: s "AB"] "A"
    , Test "{+a:1}" ["a" =: s "%B"] "%25"
    , Test "{+a:1}" ["a" =: s "\xa0\&B"] "%C2%A0"
    , Test "{+a:1}" ["a" =: s "\xd7ff\&B"] "%ED%9F%BF"
    , Test "{+a:1}" ["a" =: s "\x10000\&B"] "%F0%90%80%80"
    , Test "{+a:1}" ["a" =: l []] ""
    , Test "{+a:1}" ["a" =: l ["AB"]] "AB"
    , Test "{+a:1}" ["a" =: l ["AB", "CD"]] "AB,CD"
    , Test "{+a:1}" ["a" =: d []] ""
    , Test "{+a:1}" ["a" =: d ["AB" =: "12"]] "AB,12"
    , Test "{+a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "AB,12,CD,34"
    , Test "{+a,a}" [] ""
    , Test "{+a,a}" ["a" =: l []] ""
    , Test "{+a,a}" ["a" =: d []] ""
    , Test "{+a,a}" ["a" =: s ""] ","
    , Test "{+a,b}" ["a" =: s ""] ""
    , Test "{+a,b}" ["b" =: s ""] ""
    , Test "{+%aa}" ["%aa" =: s "A"] "A"
    , Test "{+%aa}" ["%aa" =: l ["A", "B"]] "A,B"
    , Test "{+%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A,1,B,2"
    , Test "{+%aa*}" ["%aa" =: s "A"] "A"
    , Test "{+%aa*}" ["%aa" =: l ["A", "B"]] "A,B"
    , Test "{+%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "A=1,B=2"
    , Test "{#a}" [] ""
    , Test "{#a}" ["a" =: l []] ""
    , Test "{#a}" ["a" =: d []] ""
    , Test "{#a}" ["a" =: s ""] "#"
    , Test "{#a}" ["a" =: s "A"] "#A"
    , Test "{#a}" ["a" =: s "~"] "#~"
    , Test "{#a}" ["a" =: s "%"] "#%25"
    , Test "{#a}" ["a" =: s "?"] "#?"
    , Test "{#a}" ["a" =: s "&"] "#&"
    , Test "{#a}" ["a" =: s "\xa0"] "#%C2%A0"
    , Test "{#a}" ["a" =: s "\xd7ff"] "#%ED%9F%BF"
    , Test "{#a}" ["a" =: s "\x10000"] "#%F0%90%80%80"
    , Test "{#a}" ["a" =: l ["A"]] "#A"
    , Test "{#a}" ["a" =: l ["A", "B"]] "#A,B"
    , Test "{#a}" ["a" =: l ["%"]] "#%25"
    , Test "{#a}" ["a" =: l ["\xa0"]] "#%C2%A0"
    , Test "{#a}" ["a" =: d ["A" =: "1"]] "#A,1"
    , Test "{#a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "#A,1,B,2"
    , Test "{#a}" ["a" =: d ["A" =: "%"]] "#A,%25"
    , Test "{#a}" ["a" =: d ["A" =: "\xa0"]] "#A,%C2%A0"
    , Test "{#a}" ["a" =: d ["%" =: "1"]] "#%25,1"
    , Test "{#a*}" [] ""
    , Test "{#a*}" ["a" =: s ""] "#"
    , Test "{#a*}" ["a" =: s "A"] "#A"
    , Test "{#a*}" ["a" =: l []] ""
    , Test "{#a*}" ["a" =: l ["A"]] "#A"
    , Test "{#a*}" ["a" =: l ["A", "B"]] "#A,B"
    , Test "{#a*}" ["a" =: d []] ""
    , Test "{#a*}" ["a" =: d ["A" =: "1"]] "#A=1"
    , Test "{#a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "#A=1,B=2"
    , Test "{#a:1}" [] ""
    , Test "{#a:1}" ["a" =: s ""] "#"
    , Test "{#a:1}" ["a" =: s "A"] "#A"
    , Test "{#a:1}" ["a" =: s "AB"] "#A"
    , Test "{#a:1}" ["a" =: s "%B"] "#%25"
    , Test "{#a:1}" ["a" =: s "\xa0\&B"] "#%C2%A0"
    , Test "{#a:1}" ["a" =: s "\xd7ff\&B"] "#%ED%9F%BF"
    , Test "{#a:1}" ["a" =: s "\x10000\&B"] "#%F0%90%80%80"
    , Test "{#a:1}" ["a" =: l []] ""
    , Test "{#a:1}" ["a" =: l ["AB"]] "#AB"
    , Test "{#a:1}" ["a" =: l ["AB", "CD"]] "#AB,CD"
    , Test "{#a:1}" ["a" =: d []] ""
    , Test "{#a:1}" ["a" =: d ["AB" =: "12"]] "#AB,12"
    , Test "{#a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "#AB,12,CD,34"
    , Test "{#a,a}" [] ""
    , Test "{#a,a}" ["a" =: l []] ""
    , Test "{#a,a}" ["a" =: d []] ""
    , Test "{#a,a}" ["a" =: s ""] "#,"
    , Test "{#a,b}" ["a" =: s ""] "#"
    , Test "{#a,b}" ["b" =: s ""] "#"
    , Test "{#%aa}" ["%aa" =: s "A"] "#A"
    , Test "{#%aa}" ["%aa" =: l ["A", "B"]] "#A,B"
    , Test "{#%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "#A,1,B,2"
    , Test "{#%aa*}" ["%aa" =: s "A"] "#A"
    , Test "{#%aa*}" ["%aa" =: l ["A", "B"]] "#A,B"
    , Test "{#%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "#A=1,B=2"
    , Test "{.a}" [] ""
    , Test "{.a}" ["a" =: l []] ""
    , Test "{.a}" ["a" =: d []] ""
    , Test "{.a}" ["a" =: s ""] "."
    , Test "{.a}" ["a" =: s "A"] ".A"
    , Test "{.a}" ["a" =: s "~"] ".~"
    , Test "{.a}" ["a" =: s "%"] ".%25"
    , Test "{.a}" ["a" =: s "?"] ".%3F"
    , Test "{.a}" ["a" =: s "&"] ".%26"
    , Test "{.a}" ["a" =: s "\xa0"] ".%C2%A0"
    , Test "{.a}" ["a" =: s "\xd7ff"] ".%ED%9F%BF"
    , Test "{.a}" ["a" =: s "\x10000"] ".%F0%90%80%80"
    , Test "{.a}" ["a" =: l ["A"]] ".A"
    , Test "{.a}" ["a" =: l ["A", "B"]] ".A,B"
    , Test "{.a}" ["a" =: l ["%"]] ".%25"
    , Test "{.a}" ["a" =: l ["\xa0"]] ".%C2%A0"
    , Test "{.a}" ["a" =: d ["A" =: "1"]] ".A,1"
    , Test "{.a}" ["a" =: d ["A" =: "1", "B" =: "2"]] ".A,1,B,2"
    , Test "{.a}" ["a" =: d ["A" =: "%"]] ".A,%25"
    , Test "{.a}" ["a" =: d ["A" =: "\xa0"]] ".A,%C2%A0"
    , Test "{.a}" ["a" =: d ["%" =: "1"]] ".%25,1"
    , Test "{.a*}" [] ""
    , Test "{.a*}" ["a" =: s ""] "."
    , Test "{.a*}" ["a" =: s "A"] ".A"
    , Test "{.a*}" ["a" =: l []] ""
    , Test "{.a*}" ["a" =: l ["A"]] ".A"
    , Test "{.a*}" ["a" =: l ["A", "B"]] ".A.B"
    , Test "{.a*}" ["a" =: d []] ""
    , Test "{.a*}" ["a" =: d ["A" =: "1"]] ".A=1"
    , Test "{.a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] ".A=1.B=2"
    , Test "{.a:1}" [] ""
    , Test "{.a:1}" ["a" =: s ""] "."
    , Test "{.a:1}" ["a" =: s "A"] ".A"
    , Test "{.a:1}" ["a" =: s "AB"] ".A"
    , Test "{.a:1}" ["a" =: s "%B"] ".%25"
    , Test "{.a:1}" ["a" =: s "\xa0\&B"] ".%C2%A0"
    , Test "{.a:1}" ["a" =: s "\xd7ff\&B"] ".%ED%9F%BF"
    , Test "{.a:1}" ["a" =: s "\x10000\&B"] ".%F0%90%80%80"
    , Test "{.a:1}" ["a" =: l []] ""
    , Test "{.a:1}" ["a" =: l ["AB"]] ".AB"
    , Test "{.a:1}" ["a" =: l ["AB", "CD"]] ".AB,CD"
    , Test "{.a:1}" ["a" =: d []] ""
    , Test "{.a:1}" ["a" =: d ["AB" =: "12"]] ".AB,12"
    , Test "{.a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] ".AB,12,CD,34"
    , Test "{.a,a}" [] ""
    , Test "{.a,a}" ["a" =: l []] ""
    , Test "{.a,a}" ["a" =: d []] ""
    , Test "{.a,a}" ["a" =: s ""] ".."
    , Test "{.a,b}" ["a" =: s ""] "."
    , Test "{.a,b}" ["b" =: s ""] "."
    , Test "{.%aa}" ["%aa" =: s "A"] ".A"
    , Test "{.%aa}" ["%aa" =: l ["A", "B"]] ".A,B"
    , Test "{.%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ".A,1,B,2"
    , Test "{.%aa*}" ["%aa" =: s "A"] ".A"
    , Test "{.%aa*}" ["%aa" =: l ["A", "B"]] ".A.B"
    , Test "{.%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ".A=1.B=2"
    , Test "{/a}" [] ""
    , Test "{/a}" ["a" =: l []] ""
    , Test "{/a}" ["a" =: d []] ""
    , Test "{/a}" ["a" =: s ""] "/"
    , Test "{/a}" ["a" =: s "A"] "/A"
    , Test "{/a}" ["a" =: s "~"] "/~"
    , Test "{/a}" ["a" =: s "%"] "/%25"
    , Test "{/a}" ["a" =: s "?"] "/%3F"
    , Test "{/a}" ["a" =: s "&"] "/%26"
    , Test "{/a}" ["a" =: s "\xa0"] "/%C2%A0"
    , Test "{/a}" ["a" =: s "\xd7ff"] "/%ED%9F%BF"
    , Test "{/a}" ["a" =: s "\x10000"] "/%F0%90%80%80"
    , Test "{/a}" ["a" =: l ["A"]] "/A"
    , Test "{/a}" ["a" =: l ["A", "B"]] "/A,B"
    , Test "{/a}" ["a" =: l ["%"]] "/%25"
    , Test "{/a}" ["a" =: l ["\xa0"]] "/%C2%A0"
    , Test "{/a}" ["a" =: d ["A" =: "1"]] "/A,1"
    , Test "{/a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "/A,1,B,2"
    , Test "{/a}" ["a" =: d ["A" =: "%"]] "/A,%25"
    , Test "{/a}" ["a" =: d ["A" =: "\xa0"]] "/A,%C2%A0"
    , Test "{/a}" ["a" =: d ["%" =: "1"]] "/%25,1"
    , Test "{/a*}" [] ""
    , Test "{/a*}" ["a" =: s ""] "/"
    , Test "{/a*}" ["a" =: s "A"] "/A"
    , Test "{/a*}" ["a" =: l []] ""
    , Test "{/a*}" ["a" =: l ["A"]] "/A"
    , Test "{/a*}" ["a" =: l ["A", "B"]] "/A/B"
    , Test "{/a*}" ["a" =: d []] ""
    , Test "{/a*}" ["a" =: d ["A" =: "1"]] "/A=1"
    , Test "{/a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "/A=1/B=2"
    , Test "{/a:1}" [] ""
    , Test "{/a:1}" ["a" =: s ""] "/"
    , Test "{/a:1}" ["a" =: s "A"] "/A"
    , Test "{/a:1}" ["a" =: s "AB"] "/A"
    , Test "{/a:1}" ["a" =: s "%B"] "/%25"
    , Test "{/a:1}" ["a" =: s "\xa0\&B"] "/%C2%A0"
    , Test "{/a:1}" ["a" =: s "\xd7ff\&B"] "/%ED%9F%BF"
    , Test "{/a:1}" ["a" =: s "\x10000\&B"] "/%F0%90%80%80"
    , Test "{/a:1}" ["a" =: l []] ""
    , Test "{/a:1}" ["a" =: l ["AB"]] "/AB"
    , Test "{/a:1}" ["a" =: l ["AB", "CD"]] "/AB,CD"
    , Test "{/a:1}" ["a" =: d []] ""
    , Test "{/a:1}" ["a" =: d ["AB" =: "12"]] "/AB,12"
    , Test "{/a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "/AB,12,CD,34"
    , Test "{/a,a}" [] ""
    , Test "{/a,a}" ["a" =: l []] ""
    , Test "{/a,a}" ["a" =: d []] ""
    , Test "{/a,a}" ["a" =: s ""] "//"
    , Test "{/a,b}" ["a" =: s ""] "/"
    , Test "{/a,b}" ["b" =: s ""] "/"
    , Test "{/%aa}" ["%aa" =: s "A"] "/A"
    , Test "{/%aa}" ["%aa" =: l ["A", "B"]] "/A,B"
    , Test "{/%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "/A,1,B,2"
    , Test "{/%aa*}" ["%aa" =: s "A"] "/A"
    , Test "{/%aa*}" ["%aa" =: l ["A", "B"]] "/A/B"
    , Test "{/%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "/A=1/B=2"
    , Test "{;a}" [] ""
    , Test "{;a}" ["a" =: l []] ""
    , Test "{;a}" ["a" =: d []] ""
    , Test "{;a}" ["a" =: s ""] ";a"
    , Test "{;a}" ["a" =: s "A"] ";a=A"
    , Test "{;a}" ["a" =: s "~"] ";a=~"
    , Test "{;a}" ["a" =: s "%"] ";a=%25"
    , Test "{;a}" ["a" =: s "?"] ";a=%3F"
    , Test "{;a}" ["a" =: s "&"] ";a=%26"
    , Test "{;a}" ["a" =: s "\xa0"] ";a=%C2%A0"
    , Test "{;a}" ["a" =: s "\xd7ff"] ";a=%ED%9F%BF"
    , Test "{;a}" ["a" =: s "\x10000"] ";a=%F0%90%80%80"
    , Test "{;a}" ["a" =: l ["A"]] ";a=A"
    , Test "{;a}" ["a" =: l ["A", "B"]] ";a=A,B"
    , Test "{;a}" ["a" =: l ["%"]] ";a=%25"
    , Test "{;a}" ["a" =: l ["\xa0"]] ";a=%C2%A0"
    , Test "{;a}" ["a" =: d ["A" =: "1"]] ";a=A,1"
    , Test "{;a}" ["a" =: d ["A" =: "1", "B" =: "2"]] ";a=A,1,B,2"
    , Test "{;a}" ["a" =: d ["A" =: "%"]] ";a=A,%25"
    , Test "{;a}" ["a" =: d ["A" =: "\xa0"]] ";a=A,%C2%A0"
    , Test "{;a}" ["a" =: d ["%" =: "1"]] ";a=%25,1"
    , Test "{;a*}" [] ""
    , Test "{;a*}" ["a" =: s ""] ";a"
    , Test "{;a*}" ["a" =: s "A"] ";a=A"
    , Test "{;a*}" ["a" =: l []] ""
    , Test "{;a*}" ["a" =: l ["A"]] ";a=A"
    , Test "{;a*}" ["a" =: l ["A", "B"]] ";a=A;a=B"
    , Test "{;a*}" ["a" =: d []] ""
    , Test "{;a*}" ["a" =: d ["A" =: "1"]] ";A=1"
    , Test "{;a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] ";A=1;B=2"
    , Test "{;a:1}" [] ""
    , Test "{;a:1}" ["a" =: s ""] ";a"
    , Test "{;a:1}" ["a" =: s "A"] ";a=A"
    , Test "{;a:1}" ["a" =: s "AB"] ";a=A"
    , Test "{;a:1}" ["a" =: s "%B"] ";a=%25"
    , Test "{;a:1}" ["a" =: s "\xa0\&B"] ";a=%C2%A0"
    , Test "{;a:1}" ["a" =: s "\xd7ff\&B"] ";a=%ED%9F%BF"
    , Test "{;a:1}" ["a" =: s "\x10000\&B"] ";a=%F0%90%80%80"
    , Test "{;a:1}" ["a" =: l []] ""
    , Test "{;a:1}" ["a" =: l ["AB"]] ";a=AB"
    , Test "{;a:1}" ["a" =: l ["AB", "CD"]] ";a=AB,CD"
    , Test "{;a:1}" ["a" =: d []] ""
    , Test "{;a:1}" ["a" =: d ["AB" =: "12"]] ";a=AB,12"
    , Test "{;a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] ";a=AB,12,CD,34"
    , Test "{;a,a}" [] ""
    , Test "{;a,a}" ["a" =: l []] ""
    , Test "{;a,a}" ["a" =: d []] ""
    , Test "{;a,a}" ["a" =: s ""] ";a;a"
    , Test "{;a,b}" ["a" =: s ""] ";a"
    , Test "{;a,b}" ["b" =: s ""] ";b"
    , Test "{;%aa}" ["%aa" =: s "A"] ";%aa=A"
    , Test "{;%aa}" ["%aa" =: l ["A", "B"]] ";%aa=A,B"
    , Test "{;%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ";%aa=A,1,B,2"
    , Test "{;%aa*}" ["%aa" =: s "A"] ";%aa=A"
    , Test "{;%aa*}" ["%aa" =: l ["A", "B"]] ";%aa=A;%aa=B"
    , Test "{;%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] ";A=1;B=2"
    , Test "{?a}" [] ""
    , Test "{?a}" ["a" =: l []] ""
    , Test "{?a}" ["a" =: d []] ""
    , Test "{?a}" ["a" =: s ""] "?a="
    , Test "{?a}" ["a" =: s "A"] "?a=A"
    , Test "{?a}" ["a" =: s "~"] "?a=~"
    , Test "{?a}" ["a" =: s "%"] "?a=%25"
    , Test "{?a}" ["a" =: s "?"] "?a=%3F"
    , Test "{?a}" ["a" =: s "&"] "?a=%26"
    , Test "{?a}" ["a" =: s "\xa0"] "?a=%C2%A0"
    , Test "{?a}" ["a" =: s "\xd7ff"] "?a=%ED%9F%BF"
    , Test "{?a}" ["a" =: s "\x10000"] "?a=%F0%90%80%80"
    , Test "{?a}" ["a" =: l ["A"]] "?a=A"
    , Test "{?a}" ["a" =: l ["A", "B"]] "?a=A,B"
    , Test "{?a}" ["a" =: l ["%"]] "?a=%25"
    , Test "{?a}" ["a" =: l ["\xa0"]] "?a=%C2%A0"
    , Test "{?a}" ["a" =: d ["A" =: "1"]] "?a=A,1"
    , Test "{?a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "?a=A,1,B,2"
    , Test "{?a}" ["a" =: d ["A" =: "%"]] "?a=A,%25"
    , Test "{?a}" ["a" =: d ["A" =: "\xa0"]] "?a=A,%C2%A0"
    , Test "{?a}" ["a" =: d ["%" =: "1"]] "?a=%25,1"
    , Test "{?a*}" [] ""
    , Test "{?a*}" ["a" =: s ""] "?a="
    , Test "{?a*}" ["a" =: s "A"] "?a=A"
    , Test "{?a*}" ["a" =: l []] ""
    , Test "{?a*}" ["a" =: l ["A"]] "?a=A"
    , Test "{?a*}" ["a" =: l ["A", "B"]] "?a=A&a=B"
    , Test "{?a*}" ["a" =: d []] ""
    , Test "{?a*}" ["a" =: d ["A" =: "1"]] "?A=1"
    , Test "{?a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "?A=1&B=2"
    , Test "{?a:1}" [] ""
    , Test "{?a:1}" ["a" =: s ""] "?a="
    , Test "{?a:1}" ["a" =: s "A"] "?a=A"
    , Test "{?a:1}" ["a" =: s "AB"] "?a=A"
    , Test "{?a:1}" ["a" =: s "%B"] "?a=%25"
    , Test "{?a:1}" ["a" =: s "\xa0\&B"] "?a=%C2%A0"
    , Test "{?a:1}" ["a" =: s "\xd7ff\&B"] "?a=%ED%9F%BF"
    , Test "{?a:1}" ["a" =: s "\x10000\&B"] "?a=%F0%90%80%80"
    , Test "{?a:1}" ["a" =: l []] ""
    , Test "{?a:1}" ["a" =: l ["AB"]] "?a=AB"
    , Test "{?a:1}" ["a" =: l ["AB", "CD"]] "?a=AB,CD"
    , Test "{?a:1}" ["a" =: d []] ""
    , Test "{?a:1}" ["a" =: d ["AB" =: "12"]] "?a=AB,12"
    , Test "{?a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "?a=AB,12,CD,34"
    , Test "{?a,a}" [] ""
    , Test "{?a,a}" ["a" =: l []] ""
    , Test "{?a,a}" ["a" =: d []] ""
    , Test "{?a,a}" ["a" =: s ""] "?a=&a="
    , Test "{?a,b}" ["a" =: s ""] "?a="
    , Test "{?a,b}" ["b" =: s ""] "?b="
    , Test "{?%aa}" ["%aa" =: s "A"] "?%aa=A"
    , Test "{?%aa}" ["%aa" =: l ["A", "B"]] "?%aa=A,B"
    , Test "{?%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "?%aa=A,1,B,2"
    , Test "{?%aa*}" ["%aa" =: s "A"] "?%aa=A"
    , Test "{?%aa*}" ["%aa" =: l ["A", "B"]] "?%aa=A&%aa=B"
    , Test "{?%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "?A=1&B=2"
    , Test "{&a}" [] ""
    , Test "{&a}" ["a" =: l []] ""
    , Test "{&a}" ["a" =: d []] ""
    , Test "{&a}" ["a" =: s ""] "&a="
    , Test "{&a}" ["a" =: s "A"] "&a=A"
    , Test "{&a}" ["a" =: s "~"] "&a=~"
    , Test "{&a}" ["a" =: s "%"] "&a=%25"
    , Test "{&a}" ["a" =: s "?"] "&a=%3F"
    , Test "{&a}" ["a" =: s "&"] "&a=%26"
    , Test "{&a}" ["a" =: s "\xa0"] "&a=%C2%A0"
    , Test "{&a}" ["a" =: s "\xd7ff"] "&a=%ED%9F%BF"
    , Test "{&a}" ["a" =: s "\x10000"] "&a=%F0%90%80%80"
    , Test "{&a}" ["a" =: l ["A"]] "&a=A"
    , Test "{&a}" ["a" =: l ["A", "B"]] "&a=A,B"
    , Test "{&a}" ["a" =: l ["%"]] "&a=%25"
    , Test "{&a}" ["a" =: l ["\xa0"]] "&a=%C2%A0"
    , Test "{&a}" ["a" =: d ["A" =: "1"]] "&a=A,1"
    , Test "{&a}" ["a" =: d ["A" =: "1", "B" =: "2"]] "&a=A,1,B,2"
    , Test "{&a}" ["a" =: d ["A" =: "%"]] "&a=A,%25"
    , Test "{&a}" ["a" =: d ["A" =: "\xa0"]] "&a=A,%C2%A0"
    , Test "{&a}" ["a" =: d ["%" =: "1"]] "&a=%25,1"
    , Test "{&a*}" [] ""
    , Test "{&a*}" ["a" =: s ""] "&a="
    , Test "{&a*}" ["a" =: s "A"] "&a=A"
    , Test "{&a*}" ["a" =: l []] ""
    , Test "{&a*}" ["a" =: l ["A"]] "&a=A"
    , Test "{&a*}" ["a" =: l ["A", "B"]] "&a=A&a=B"
    , Test "{&a*}" ["a" =: d []] ""
    , Test "{&a*}" ["a" =: d ["A" =: "1"]] "&A=1"
    , Test "{&a*}" ["a" =: d ["A" =: "1", "B" =: "2"]] "&A=1&B=2"
    , Test "{&a:1}" [] ""
    , Test "{&a:1}" ["a" =: s ""] "&a="
    , Test "{&a:1}" ["a" =: s "A"] "&a=A"
    , Test "{&a:1}" ["a" =: s "AB"] "&a=A"
    , Test "{&a:1}" ["a" =: s "%B"] "&a=%25"
    , Test "{&a:1}" ["a" =: s "\xa0\&B"] "&a=%C2%A0"
    , Test "{&a:1}" ["a" =: s "\xd7ff\&B"] "&a=%ED%9F%BF"
    , Test "{&a:1}" ["a" =: s "\x10000\&B"] "&a=%F0%90%80%80"
    , Test "{&a:1}" ["a" =: l []] ""
    , Test "{&a:1}" ["a" =: l ["AB"]] "&a=AB"
    , Test "{&a:1}" ["a" =: l ["AB", "CD"]] "&a=AB,CD"
    , Test "{&a:1}" ["a" =: d []] ""
    , Test "{&a:1}" ["a" =: d ["AB" =: "12"]] "&a=AB,12"
    , Test "{&a:1}" ["a" =: d ["AB" =: "12", "CD" =: "34"]] "&a=AB,12,CD,34"
    , Test "{&a,a}" [] ""
    , Test "{&a,a}" ["a" =: l []] ""
    , Test "{&a,a}" ["a" =: d []] ""
    , Test "{&a,a}" ["a" =: s ""] "&a=&a="
    , Test "{&a,b}" ["a" =: s ""] "&a="
    , Test "{&a,b}" ["b" =: s ""] "&b="
    , Test "{&%aa}" ["%aa" =: s "A"] "&%aa=A"
    , Test "{&%aa}" ["%aa" =: l ["A", "B"]] "&%aa=A,B"
    , Test "{&%aa}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "&%aa=A,1,B,2"
    , Test "{&%aa*}" ["%aa" =: s "A"] "&%aa=A"
    , Test "{&%aa*}" ["%aa" =: l ["A", "B"]] "&%aa=A&%aa=B"
    , Test "{&%aa*}" ["%aa" =: d ["A" =: "1", "B" =: "2"]] "&A=1&B=2"
    ]
  , let values = ["%AA" =: s "1", "%Aa" =: s "2", "%aA" =: s "3", "%aa" =: s "4"]
    in
      [ Test "{%AA}" values "1"
      , Test "{%Aa}" values "2"
      , Test "{%aA}" values "3"
      , Test "{%aa}" values "4"
          -- This comment forces Brittany to use a multi-line layout.
      ]
  , let values = ["a" =: s "abcdefghijklmnopqrstuvwxyz"]
    in
      [ Test "{a:1}" values "a"
      , Test "{a:5}" values "abcde"
      , Test "{a:10}" values "abcdefghij"
      , Test "{a:15}" values "abcdefghijklmno"
      , Test "{a:20}" values "abcdefghijklmnopqrst"
      , Test "{a:25}" values "abcdefghijklmnopqrstuvwxy"
      , Test "{a:26}" values "abcdefghijklmnopqrstuvwxyz"
      , Test "{a:30}" values "abcdefghijklmnopqrstuvwxyz"
      ]
  , let values = ["a" =: l []]
    in
      [ Test "{a}" values ""
      , Test "{+a}" values ""
      , Test "{#a}" values ""
      , Test "{.a}" values ""
      , Test "{/a}" values ""
      , Test "{;a}" values ""
      , Test "{?a}" values ""
      , Test "{&a}" values ""
      ]
  , let values = ["a" =: d []]
    in
      [ Test "{a}" values ""
      , Test "{+a}" values ""
      , Test "{#a}" values ""
      , Test "{.a}" values ""
      , Test "{/a}" values ""
      , Test "{;a}" values ""
      , Test "{?a}" values ""
      , Test "{&a}" values ""
      ]
  , let values = ["a" =: s ""]
    in
      [ Test "{a}" values ""
      , Test "{+a}" values ""
      , Test "{#a}" values "#"
      , Test "{.a}" values "."
      , Test "{/a}" values "/"
      , Test "{;a}" values ";a"
      , Test "{?a}" values "?a="
      , Test "{&a}" values "&a="
      ]
  , let values = ["a" =: s "A"]
    in
      [ Test "{a}" values "A"
      , Test "{+a}" values "A"
      , Test "{#a}" values "#A"
      , Test "{.a}" values ".A"
      , Test "{/a}" values "/A"
      , Test "{;a}" values ";a=A"
      , Test "{?a}" values "?a=A"
      , Test "{&a}" values "&a=A"
      ]
  , let values = ["b" =: s "B"]
    in
      [ Test "{a,b}" values "B"
      , Test "{+a,b}" values "B"
      , Test "{#a,b}" values "#B"
      , Test "{.a,b}" values ".B"
      , Test "{/a,b}" values "/B"
      , Test "{;a,b}" values ";b=B"
      , Test "{?a,b}" values "?b=B"
      , Test "{&a,b}" values "&b=B"
      ]
  , let values = ["a" =: s ""]
    in
      [ Test "{a,a}" values ","
      , Test "{+a,a}" values ","
      , Test "{#a,a}" values "#,"
      , Test "{.a,a}" values ".."
      , Test "{/a,a}" values "//"
      , Test "{;a,a}" values ";a;a"
      , Test "{?a,a}" values "?a=&a="
      , Test "{&a,a}" values "&a=&a="
      ]
  , let values = ["a" =: s "A", "b" =: s "B"]
    in
      [ Test "{a,b}" values "A,B"
      , Test "{+a,b}" values "A,B"
      , Test "{#a,b}" values "#A,B"
      , Test "{.a,b}" values ".A.B"
      , Test "{/a,b}" values "/A/B"
      , Test "{;a,b}" values ";a=A;b=B"
      , Test "{?a,b}" values "?a=A&b=B"
      , Test "{&a,b}" values "&a=A&b=B"
      ]
  , let values = ["a" =: d ["K! \xa0\xd7ff\x10000" =: "V! \xa0\xd7ff\x10000"]]
    in
      [ Test "{a*}" values "K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"
      , Test "{+a*}" values "K!%20%C2%A0%ED%9F%BF%F0%90%80%80=V!%20%C2%A0%ED%9F%BF%F0%90%80%80"
      , Test "{#a*}" values "#K!%20%C2%A0%ED%9F%BF%F0%90%80%80=V!%20%C2%A0%ED%9F%BF%F0%90%80%80"
      , Test "{.a*}" values ".K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"
      , Test "{/a*}" values "/K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"
      , Test "{;a*}" values ";K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"
      , Test "{?a*}" values "?K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"
      , Test "{&a*}" values "&K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80"
      ]
  , let
      values =
        [ "empty" =: s ""
        , "hello" =: s "Hello World!"
        , "keys" =: d ["semi" =: ";", "dot" =: ".", "comma" =: ","]
        , "list" =: l ["red", "green", "blue"]
        , "path" =: s "/foo/bar"
        , "var" =: s "value"
        , "x" =: s "1024"
        , "y" =: s "768"
        ]
    in
      [ Test "{var}" values "value"
      , Test "{hello}" values "Hello%20World%21"
      , Test "{+var}" values "value"
      , Test "{+hello}" values "Hello%20World!"
      , Test "{+path}/here" values "/foo/bar/here"
      , Test "here?ref={+path}" values "here?ref=/foo/bar"
      , Test "X{#var}" values "X#value"
      , Test "X{#hello}" values "X#Hello%20World!"
      , Test "map?{x,y}" values "map?1024,768"
      , Test "{x,hello,y}" values "1024,Hello%20World%21,768"
      , Test "{+x,hello,y}" values "1024,Hello%20World!,768"
      , Test "{+path,x}/here" values "/foo/bar,1024/here"
      , Test "{#x,hello,y}" values "#1024,Hello%20World!,768"
      , Test "{#path,x}/here" values "#/foo/bar,1024/here"
      , Test "X{.var}" values "X.value"
      , Test "X{.x,y}" values "X.1024.768"
      , Test "{/var}" values "/value"
      , Test "{/var,x}/here" values "/value/1024/here"
      , Test "{;x,y}" values ";x=1024;y=768"
      , Test "{;x,y,empty}" values ";x=1024;y=768;empty"
      , Test "{?x,y}" values "?x=1024&y=768"
      , Test "{?x,y,empty}" values "?x=1024&y=768&empty="
      , Test "?fixed=yes{&x}" values "?fixed=yes&x=1024"
      , Test "{&x,y,empty}" values "&x=1024&y=768&empty="
      , Test "{var:3}" values "val"
      , Test "{var:30}" values "value"
      , Test "{list}" values "red,green,blue"
      , Test "{list*}" values "red,green,blue"
      , Test "{keys}" values "comma,%2C,dot,.,semi,%3B"
      , Test "{keys*}" values "comma=%2C,dot=.,semi=%3B"
      , Test "{+path:6}/here" values "/foo/b/here"
      , Test "{+list}" values "red,green,blue"
      , Test "{+list*}" values "red,green,blue"
      , Test "{+keys}" values "comma,,,dot,.,semi,;"
      , Test "{+keys*}" values "comma=,,dot=.,semi=;"
      , Test "{#path:6}/here" values "#/foo/b/here"
      , Test "{#list}" values "#red,green,blue"
      , Test "{#list*}" values "#red,green,blue"
      , Test "{#keys}" values "#comma,,,dot,.,semi,;"
      , Test "{#keys*}" values "#comma=,,dot=.,semi=;"
      , Test "X{.var:3}" values "X.val"
      , Test "X{.list}" values "X.red,green,blue"
      , Test "X{.list*}" values "X.red.green.blue"
      , Test "X{.keys}" values "X.comma,%2C,dot,.,semi,%3B"
      , Test "X{.keys*}" values "X.comma=%2C.dot=..semi=%3B"
      , Test "{/var:1,var}" values "/v/value"
      , Test "{/list}" values "/red,green,blue"
      , Test "{/list*}" values "/red/green/blue"
      , Test "{/list*,path:4}" values "/red/green/blue/%2Ffoo"
      , Test "{/keys}" values "/comma,%2C,dot,.,semi,%3B"
      , Test "{/keys*}" values "/comma=%2C/dot=./semi=%3B"
      , Test "{;hello:5}" values ";hello=Hello"
      , Test "{;list}" values ";list=red,green,blue"
      , Test "{;list*}" values ";list=red;list=green;list=blue"
      , Test "{;keys}" values ";keys=comma,%2C,dot,.,semi,%3B"
      , Test "{;keys*}" values ";comma=%2C;dot=.;semi=%3B"
      , Test "{?var:3}" values "?var=val"
      , Test "{?list}" values "?list=red,green,blue"
      , Test "{?list*}" values "?list=red&list=green&list=blue"
      , Test "{?keys}" values "?keys=comma,%2C,dot,.,semi,%3B"
      , Test "{?keys*}" values "?comma=%2C&dot=.&semi=%3B"
      , Test "{&var:3}" values "&var=val"
      , Test "{&list}" values "&list=red,green,blue"
      , Test "{&list*}" values "&list=red&list=green&list=blue"
      , Test "{&keys}" values "&keys=comma,%2C,dot,.,semi,%3B"
      , Test "{&keys*}" values "&comma=%2C&dot=.&semi=%3B"
      ]
  , let values = ["var" =: s "value", "semi" =: s ";"]
    in
      [ Test "{var}" values "value"
      , Test "{var:20}" values "value"
      , Test "{var:3}" values "val"
      , Test "{semi}" values "%3B"
      , Test "{semi:2}" values "%3B"
          -- This comment forces Brittany to use a multi-line layout.
      ]
  , let values = ["year" =: l ["1965", "2000", "2012"], "dom" =: l ["example", "com"]]
    in
      [ Test "find{?year*}" values "find?year=1965&year=2000&year=2012"
      , Test "www{.dom*}" values "www.example.com"
          -- This comment forces Brittany to use a multi-line layout.
      ]
  , let
      values =
        [ "base" =: s "http://example.com/home/"
        , "count" =: l ["one", "two", "three"]
        , "dom" =: l ["example", "com"]
        , "dub" =: s "me/too"
        , "empty_keys" =: d []
        , "empty" =: s ""
        , "half" =: s "50%"
        , "hello" =: s "Hello World!"
        , "keys" =: d ["semi" =: ";", "dot" =: ".", "comma" =: ","]
        , "list" =: l ["red", "green", "blue"]
        , "path" =: s "/foo/bar"
        , "v" =: s "6"
        , "var" =: s "value"
        , "who" =: s "fred"
        , "x" =: s "1024"
        , "y" =: s "768"
        ]
    in
      [ Test "{count}" values "one,two,three"
      , Test "{count*}" values "one,two,three"
      , Test "{/count}" values "/one,two,three"
      , Test "{/count*}" values "/one/two/three"
      , Test "{;count}" values ";count=one,two,three"
      , Test "{;count*}" values ";count=one;count=two;count=three"
      , Test "{?count}" values "?count=one,two,three"
      , Test "{?count*}" values "?count=one&count=two&count=three"
      , Test "{&count*}" values "&count=one&count=two&count=three"
      , Test "{var}" values "value"
      , Test "{hello}" values "Hello%20World%21"
      , Test "{half}" values "50%25"
      , Test "O{empty}X" values "OX"
      , Test "O{undef}X" values "OX"
      , Test "{x,y}" values "1024,768"
      , Test "{x,hello,y}" values "1024,Hello%20World%21,768"
      , Test "?{x,empty}" values "?1024,"
      , Test "?{x,undef}" values "?1024"
      , Test "?{undef,y}" values "?768"
      , Test "{var:3}" values "val"
      , Test "{var:30}" values "value"
      , Test "{list}" values "red,green,blue"
      , Test "{list*}" values "red,green,blue"
      , Test "{keys}" values "comma,%2C,dot,.,semi,%3B"
      , Test "{keys*}" values "comma=%2C,dot=.,semi=%3B"
      , Test "{+var}" values "value"
      , Test "{+hello}" values "Hello%20World!"
      , Test "{+half}" values "50%25"
      , Test "{base}index" values "http%3A%2F%2Fexample.com%2Fhome%2Findex"
      , Test "{+base}index" values "http://example.com/home/index"
      , Test "O{+empty}X" values "OX"
      , Test "O{+undef}X" values "OX"
      , Test "{+path}/here" values "/foo/bar/here"
      , Test "here?ref={+path}" values "here?ref=/foo/bar"
      , Test "up{+path}{var}/here" values "up/foo/barvalue/here"
      , Test "{+x,hello,y}" values "1024,Hello%20World!,768"
      , Test "{+path,x}/here" values "/foo/bar,1024/here"
      , Test "{+path:6}/here" values "/foo/b/here"
      , Test "{+list}" values "red,green,blue"
      , Test "{+list*}" values "red,green,blue"
      , Test "{+keys}" values "comma,,,dot,.,semi,;"
      , Test "{+keys*}" values "comma=,,dot=.,semi=;"
      , Test "{#var}" values "#value"
      , Test "{#hello}" values "#Hello%20World!"
      , Test "{#half}" values "#50%25"
      , Test "foo{#empty}" values "foo#"
      , Test "foo{#undef}" values "foo"
      , Test "{#x,hello,y}" values "#1024,Hello%20World!,768"
      , Test "{#path,x}/here" values "#/foo/bar,1024/here"
      , Test "{#path:6}/here" values "#/foo/b/here"
      , Test "{#list}" values "#red,green,blue"
      , Test "{#list*}" values "#red,green,blue"
      , Test "{#keys}" values "#comma,,,dot,.,semi,;"
      , Test "{#keys*}" values "#comma=,,dot=.,semi=;"
      , Test "{.who}" values ".fred"
      , Test "{.who,who}" values ".fred.fred"
      , Test "{.half,who}" values ".50%25.fred"
      , Test "www{.dom*}" values "www.example.com"
      , Test "X{.var}" values "X.value"
      , Test "X{.empty}" values "X."
      , Test "X{.undef}" values "X"
      , Test "X{.var:3}" values "X.val"
      , Test "X{.list}" values "X.red,green,blue"
      , Test "X{.list*}" values "X.red.green.blue"
      , Test "X{.keys}" values "X.comma,%2C,dot,.,semi,%3B"
      , Test "X{.keys*}" values "X.comma=%2C.dot=..semi=%3B"
      , Test "X{.empty_keys}" values "X"
      , Test "X{.empty_keys*}" values "X"
      , Test "{/who}" values "/fred"
      , Test "{/who,who}" values "/fred/fred"
      , Test "{/half,who}" values "/50%25/fred"
      , Test "{/who,dub}" values "/fred/me%2Ftoo"
      , Test "{/var}" values "/value"
      , Test "{/var,empty}" values "/value/"
      , Test "{/var,undef}" values "/value"
      , Test "{/var,x}/here" values "/value/1024/here"
      , Test "{/var:1,var}" values "/v/value"
      , Test "{/list}" values "/red,green,blue"
      , Test "{/list*}" values "/red/green/blue"
      , Test "{/list*,path:4}" values "/red/green/blue/%2Ffoo"
      , Test "{/keys}" values "/comma,%2C,dot,.,semi,%3B"
      , Test "{/keys*}" values "/comma=%2C/dot=./semi=%3B"
      , Test "{;who}" values ";who=fred"
      , Test "{;half}" values ";half=50%25"
      , Test "{;empty}" values ";empty"
      , Test "{;v,empty,who}" values ";v=6;empty;who=fred"
      , Test "{;v,bar,who}" values ";v=6;who=fred"
      , Test "{;x,y}" values ";x=1024;y=768"
      , Test "{;x,y,empty}" values ";x=1024;y=768;empty"
      , Test "{;x,y,undef}" values ";x=1024;y=768"
      , Test "{;hello:5}" values ";hello=Hello"
      , Test "{;list}" values ";list=red,green,blue"
      , Test "{;list*}" values ";list=red;list=green;list=blue"
      , Test "{;keys}" values ";keys=comma,%2C,dot,.,semi,%3B"
      , Test "{;keys*}" values ";comma=%2C;dot=.;semi=%3B"
      , Test "{?who}" values "?who=fred"
      , Test "{?half}" values "?half=50%25"
      , Test "{?x,y}" values "?x=1024&y=768"
      , Test "{?x,y,empty}" values "?x=1024&y=768&empty="
      , Test "{?x,y,undef}" values "?x=1024&y=768"
      , Test "{?var:3}" values "?var=val"
      , Test "{?list}" values "?list=red,green,blue"
      , Test "{?list*}" values "?list=red&list=green&list=blue"
      , Test "{?keys}" values "?keys=comma,%2C,dot,.,semi,%3B"
      , Test "{?keys*}" values "?comma=%2C&dot=.&semi=%3B"
      , Test "{&who}" values "&who=fred"
      , Test "{&half}" values "&half=50%25"
      , Test "?fixed=yes{&x}" values "?fixed=yes&x=1024"
      , Test "{&x,y,empty}" values "&x=1024&y=768&empty="
      , Test "{&x,y,undef}" values "&x=1024&y=768"
      , Test "{&var:3}" values "&var=val"
      , Test "{&list}" values "&list=red,green,blue"
      , Test "{&list*}" values "&list=red&list=green&list=blue"
      , Test "{&keys}" values "&keys=comma,%2C,dot,.,semi,%3B"
      , Test "{&keys*}" values "&comma=%2C&dot=.&semi=%3B"
      ]
  ]

data Test = Test
  { testInput :: String
  , testValues :: [(String, Burrito.Value)]
  , testOutput :: Output
  } deriving (Eq, Show)

runTest :: Test -> Hspec.Expectation
runTest test =
  case (Burrito.parse $ testInput test, unwrapOutput $ testOutput test) of
    (Nothing, Nothing) -> pure ()
    (Nothing, Just _) -> Hspec.expectationFailure "should have parsed"
    (Just _, Nothing) -> Hspec.expectationFailure "should not have parsed"
    (Just template, Just expected) -> do
      let
        values = testValues test
        actual = Burrito.expand values template
      actual `Hspec.shouldBe` expected
      Burrito.parse (Burrito.render template) `Hspec.shouldBe` Just template
      let
        relevant =
          List.sort $ keepRelevant (templateVariables template) values
      Monad.when (isMatchable template relevant) $ do
        let matches = List.sort <$> Burrito.match expected template
        matches `Hspec.shouldSatisfy` elem relevant

isMatchable :: Template.Template -> [(String, Burrito.Value)] -> Bool
isMatchable template values =
  (not . any (isAsterisk . Variable.modifier) $ templateVariables template)
    && all (isString . snd) values

isString :: Burrito.Value -> Bool
isString value = case value of
  Value.String _ -> True
  _ -> False

isAsterisk :: Modifier.Modifier -> Bool
isAsterisk modifier = case modifier of
  Modifier.Asterisk -> True
  _ -> False

keepRelevant
  :: Set.Set Variable.Variable
  -> [(String, Burrito.Value)]
  -> [(String, Burrito.Value)]
keepRelevant variables =
  let
    vs =
      Map.fromListWith
          (\mx my -> case (mx, my) of
            (Just x, Just y) -> Just $ max x y
            _ -> Nothing
          )
        . fmap
            (\v ->
              ( Render.builderToString . Render.name $ Variable.name v
              , case Variable.modifier v of
                Modifier.Colon n -> Just $ MaxLength.count n
                _ -> Nothing
              )
            )
        $ Set.toList variables
  in
    Maybe.mapMaybe $ \(k, v) -> do
      m <- Map.lookup k vs
      pure . (,) k $ case m of
        Nothing -> v
        Just n -> case v of
          Value.String t -> Value.String $ Text.take n t
          _ -> v

templateVariables :: Template.Template -> Set.Set Variable.Variable
templateVariables =
  Set.fromList
    . concatMap
        (\token -> case token of
          Token.Expression expression ->
            NonEmpty.toList $ Expression.variables expression
          Token.Literal _ -> []
        )
    . Template.tokens

newtype Output = Output
  { unwrapOutput :: Maybe String
  } deriving (Eq, Show)

instance String.IsString Output where
  fromString = Output . Just

noParse :: Output
noParse = Output Nothing

(=:) :: a -> b -> (a, b)
(=:) = (,)

s :: String -> Burrito.Value
s = Burrito.stringValue

l :: [String] -> Burrito.Value
l = Burrito.listValue

d :: [(String, String)] -> Burrito.Value
d = Burrito.dictionaryValue

newtype Template = Template
  { unwrapTemplate :: Template.Template
  } deriving (Eq)

instance Show Template where
  show (Template template) =
    unwords [show $ Burrito.render template, "{-", show template, "-}"]

instance QC.Arbitrary Template where
  arbitrary = Template <$> arbitraryTemplate
  shrink = fmap Template . shrinkTemplate . unwrapTemplate

type Shrink a = a -> [a]

arbitraryTemplate :: QC.Gen Template.Template
arbitraryTemplate = Template.Template . simplify <$> QC.listOf arbitraryToken

shrinkTemplate :: Shrink Template.Template
shrinkTemplate =
  fmap (Template.Template . simplify)
    . QC.shrinkList shrinkToken
    . Template.tokens

simplify :: [Token.Token] -> [Token.Token]
simplify tokens = case tokens of
  t1 : t2 : ts -> case (t1, t2) of
    (Token.Literal l1, Token.Literal l2) ->
      simplify $ Token.Literal (appendLiteral l1 l2) : ts
    _ -> t1 : simplify (t2 : ts)
  _ -> tokens

appendLiteral :: Literal.Literal -> Literal.Literal -> Literal.Literal
appendLiteral x y =
  Literal.Literal $ Literal.characters x <> Literal.characters y

arbitraryToken :: QC.Gen Token.Token
arbitraryToken = QC.oneof
  [ Token.Expression <$> arbitraryExpression
  , Token.Literal <$> arbitraryLiteral
  ]

shrinkToken :: Shrink Token.Token
shrinkToken x = case x of
  Token.Expression y -> Token.Expression <$> shrinkExpression y
  Token.Literal y -> Token.Literal <$> shrinkLiteral y

arbitraryExpression :: QC.Gen Expression.Expression
arbitraryExpression =
  Expression.Expression
    <$> arbitraryOperator
    <*> arbitraryNonEmpty arbitraryVariable

shrinkExpression :: Shrink Expression.Expression
shrinkExpression x = uncurry Expression.Expression <$> shrinkTuple
  shrinkOperator
  (shrinkNonEmpty shrinkVariable)
  (Expression.operator x, Expression.variables x)

arbitraryOperator :: QC.Gen Operator.Operator
arbitraryOperator = QC.elements
  [ Operator.Ampersand
  , Operator.FullStop
  , Operator.None
  , Operator.NumberSign
  , Operator.PlusSign
  , Operator.QuestionMark
  , Operator.Semicolon
  , Operator.Solidus
  ]

shrinkOperator :: Shrink Operator.Operator
shrinkOperator x = case x of
  Operator.None -> []
  _ -> [Operator.None]

arbitraryNonEmpty :: QC.Gen a -> QC.Gen (NonEmpty.NonEmpty a)
arbitraryNonEmpty g = (NonEmpty.:|) <$> g <*> QC.listOf g

shrinkNonEmpty :: Shrink a -> Shrink (NonEmpty.NonEmpty a)
shrinkNonEmpty f x = uncurry (NonEmpty.:|)
  <$> shrinkTuple f (QC.shrinkList f) (NonEmpty.head x, NonEmpty.tail x)

arbitraryVariable :: QC.Gen Variable.Variable
arbitraryVariable = Variable.Variable <$> arbitraryName <*> arbitraryModifier

shrinkVariable :: Shrink Variable.Variable
shrinkVariable variable = uncurry Variable.Variable <$> shrinkTuple
  shrinkName
  shrinkModifier
  (Variable.name variable, Variable.modifier variable)

shrinkTuple :: Shrink a -> Shrink b -> Shrink (a, b)
shrinkTuple f g (x, y) =
  fmap (\a -> (a, y)) (f x) <> fmap (\b -> (x, b)) (g y)

arbitraryName :: QC.Gen Name.Name
arbitraryName = Name.Name <$> arbitraryNonEmpty arbitraryField

shrinkName :: Shrink Name.Name
shrinkName = fmap Name.Name . shrinkNonEmpty shrinkField . Name.fields

arbitraryField :: QC.Gen Field.Field
arbitraryField = Field.Field <$> arbitraryNonEmpty arbitraryFieldCharacter

shrinkField :: Shrink Field.Field
shrinkField =
  fmap Field.Field . shrinkNonEmpty shrinkFieldCharacter . Field.characters

arbitraryFieldCharacter :: QC.Gen (Character.Character Field.Field)
arbitraryFieldCharacter = QC.oneof
  [ Character.Encoded <$> arbitraryDigit <*> arbitraryDigit
  , Character.Unencoded <$> QC.suchThat QC.arbitrary Parse.isFieldCharacter
  ]

shrinkFieldCharacter :: Shrink (Character.Character Field.Field)
shrinkFieldCharacter x = case x of
  Character.Encoded y z ->
    uncurry Character.Encoded <$> shrinkTuple shrinkDigit shrinkDigit (y, z)
  Character.Unencoded y ->
    fmap Character.Unencoded . filter Parse.isFieldCharacter $ QC.shrink y

arbitraryDigit :: QC.Gen Digit.Digit
arbitraryDigit = QC.oneof
  [ pure Digit.Ox0
  , pure Digit.Ox1
  , pure Digit.Ox2
  , pure Digit.Ox3
  , pure Digit.Ox4
  , pure Digit.Ox5
  , pure Digit.Ox6
  , pure Digit.Ox7
  , pure Digit.Ox8
  , pure Digit.Ox9
  , Digit.OxA <$> arbitraryCase
  , Digit.OxB <$> arbitraryCase
  , Digit.OxC <$> arbitraryCase
  , Digit.OxD <$> arbitraryCase
  , Digit.OxE <$> arbitraryCase
  , Digit.OxF <$> arbitraryCase
  ]

shrinkDigit :: Shrink Digit.Digit
shrinkDigit x = case x of
  Digit.Ox0 -> []
  Digit.Ox1 -> [Digit.Ox0]
  Digit.Ox2 -> [Digit.Ox0]
  Digit.Ox3 -> [Digit.Ox0]
  Digit.Ox4 -> [Digit.Ox0]
  Digit.Ox5 -> [Digit.Ox0]
  Digit.Ox6 -> [Digit.Ox0]
  Digit.Ox7 -> [Digit.Ox0]
  Digit.Ox8 -> [Digit.Ox0]
  Digit.Ox9 -> [Digit.Ox0]
  Digit.OxA y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)
  Digit.OxB y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)
  Digit.OxC y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)
  Digit.OxD y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)
  Digit.OxE y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)
  Digit.OxF y -> Digit.Ox0 : fmap Digit.OxA (shrinkCase y)

arbitraryCase :: QC.Gen Case.Case
arbitraryCase = QC.elements [Case.Lower, Case.Upper]

shrinkCase :: Shrink Case.Case
shrinkCase x = case x of
  Case.Lower -> []
  Case.Upper -> [Case.Lower]

arbitraryModifier :: QC.Gen Modifier.Modifier
arbitraryModifier = QC.oneof
  [ pure Modifier.Asterisk
  , Modifier.Colon <$> arbitraryMaxLength
  , pure Modifier.None
  ]

shrinkModifier :: Shrink Modifier.Modifier
shrinkModifier x = case x of
  Modifier.Asterisk -> [Modifier.None]
  Modifier.Colon y -> Modifier.None : fmap Modifier.Colon (shrinkMaxLength y)
  Modifier.None -> []

arbitraryMaxLength :: QC.Gen MaxLength.MaxLength
arbitraryMaxLength =
  MaxLength.MaxLength <$> QC.suchThat QC.arbitrary Parse.isMaxLength

shrinkMaxLength :: Shrink MaxLength.MaxLength
shrinkMaxLength =
  fmap MaxLength.MaxLength
    . filter Parse.isMaxLength
    . QC.shrink
    . MaxLength.count

arbitraryLiteral :: QC.Gen Literal.Literal
arbitraryLiteral =
  Literal.Literal <$> arbitraryNonEmpty arbitraryLiteralCharacter

shrinkLiteral :: Shrink Literal.Literal
shrinkLiteral =
  fmap Literal.Literal
    . shrinkNonEmpty shrinkLiteralCharacter
    . Literal.characters

arbitraryLiteralCharacter :: QC.Gen (Character.Character Literal.Literal)
arbitraryLiteralCharacter = QC.oneof
  [ Character.Encoded <$> arbitraryDigit <*> arbitraryDigit
  , Character.Unencoded <$> QC.suchThat QC.arbitrary Parse.isLiteralCharacter
  ]

shrinkLiteralCharacter :: Shrink (Character.Character Literal.Literal)
shrinkLiteralCharacter x = case x of
  Character.Encoded y z ->
    uncurry Character.Encoded <$> shrinkTuple shrinkDigit shrinkDigit (y, z)
  Character.Unencoded y ->
    fmap Character.Unencoded . filter Parse.isLiteralCharacter $ QC.shrink y