packages feed

burrito-2.0.1.3: source/test-suite/Main.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# OPTIONS_GHC -Wno-monomorphism-restriction #-}

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 (show 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 (show 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 $ show template, "{-", show (Template.tokens 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