packages feed

burrito 1.2.0.4 → 2.0.0.0

raw patch · 46 files changed

+2823/−2755 lines, 46 filesdep ~basedep ~containersdep ~template-haskellPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, containers, template-haskell, text

API changes (from Hackage documentation)

Files

LICENSE.markdown view
@@ -1,15 +1,21 @@-ISC License (ISC)+MIT License -Copyright 2021 Taylor Fausak+Copyright (c) 2022 Taylor Fausak -Permission to use, copy, modify, and/or distribute this software for any-purpose with or without fee is hereby granted, provided that the above-copyright notice and this permission notice appear in all copies.+Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH-REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND-FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,-INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM-LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR-OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR-PERFORMANCE OF THIS SOFTWARE.+The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
burrito.cabal view
@@ -1,7 +1,7 @@-cabal-version: >= 1.10+cabal-version: 2.2  name: burrito-version: 1.2.0.4+version: 2.0.0.0  synopsis: Parse and render URI templates. description:@@ -25,23 +25,62 @@ category: Network extra-source-files: README.markdown license-file: LICENSE.markdown-license: ISC+license: MIT maintainer: Taylor Fausak  source-repository head   location: https://github.com/tfausak/burrito   type: git -library+flag pedantic+  default: False+  description: Enables @-Werror@, which turns warnings into errors.+  manual: True++common library   build-depends:-    base >= 4.13.0 && < 4.16+    , base >= 4.13.0 && < 4.17     , bytestring >= 0.10.10 && < 0.12     , containers >= 0.6.2 && < 0.7     , parsec >= 3.1.14 && < 3.2-    , template-haskell >= 2.15.0 && < 2.18+    , template-haskell >= 2.15.0 && < 2.19     , text >= 1.2.4 && < 1.3     , transformers >= 0.5.6 && < 0.6   default-language: Haskell2010+  ghc-options:+    -Weverything+    -Wno-all-missed-specialisations+    -Wno-implicit-prelude+    -Wno-missed-specialisations+    -Wno-missing-deriving-strategies+    -Wno-missing-exported-signatures+    -Wno-safe+    -Wno-unsafe++  if flag(pedantic)+    ghc-options: -Werror++  if impl(ghc >= 8.10)+    ghc-options:+      -Wno-missing-safe-haskell-mode+      -Wno-prepositive-qualified-module++  if impl(ghc >= 9.2)+    ghc-options:+      -Wno-missing-kind-signatures++common executable+  import: library++  build-depends: burrito+  ghc-options:+    -rtsopts+    -threaded+    -Wno-unused-packages++library+  import: library+   exposed-modules:     Burrito     Burrito.Internal.Expand@@ -64,28 +103,14 @@     Burrito.Internal.Type.Token     Burrito.Internal.Type.Value     Burrito.Internal.Type.Variable-  ghc-options:-    -Weverything-    -Wno-implicit-prelude-    -Wno-missing-deriving-strategies-    -Wno-missing-exported-signatures-    -Wno-safe-  hs-source-dirs: src/lib--  if impl(ghc >= 8.10)-    ghc-options:-      -Wno-missing-safe-haskell-mode-      -Wno-prepositive-qualified-module+  hs-source-dirs: source/library  test-suite test+  import: executable+   build-depends:-    base -any-    , burrito -any-    , containers -any     , hspec >= 2.7.6 && < 2.10     , QuickCheck >= 2.13.2 && < 2.15-    , text -any-  default-language: Haskell2010-  hs-source-dirs: src/test+  hs-source-dirs: source/test-suite   main-is: Main.hs   type: exitcode-stdio-1.0
+ source/library/Burrito.hs view
@@ -0,0 +1,64 @@+-- | Burrito is a Haskell library for parsing and rendering URI templates.+--+-- According to [RFC 6570](https://tools.ietf.org/html/rfc6570): "A URI+-- Template is a compact sequence of characters for describing a range of+  -- Uniform Resource Identifiers through variable expansion." Burrito+  -- implements URI templates according to the specification in that RFC.+--+-- The term "uniform resource identifiers" (URI) is often used interchangeably+-- with other related terms like "internationalized resource identifier" (IRI),+-- "uniform resource locator" (URL), and "uniform resource name" (URN). Burrito+-- can be used for all of these. If you want to get technical, its input must+-- be a valid IRI and its output will be a valid URI or URN.+--+-- Although Burrito is primarily intended to be used with HTTP and HTTPS URIs,+-- it should work with other schemes as well.+--+-- If you're not already familiar with URI templates, I recommend reading the+-- overview of the RFC. It's short, to the point, and easy to understand.+--+-- Assuming you're familiar with URI templates, here's a simple example to show+-- you how Burrito works:+--+-- >>> import Burrito+-- >>> let Just template = parse "http://example/search{?query}"+-- >>> expand [("query", stringValue "chorizo")] template+-- "http://example.com/search?query=chorizo"+--+-- In short, use @parse@ to parse templates and @expand@ to render them.+module Burrito+  ( Parse.parse+  , Template.render+  , Expand.expand+  , Expand.expandWith+  , Match.match+  , TH.uriTemplate+  , Template.Template+  , Value.Value+  , stringValue+  , listValue+  , dictionaryValue+  ) where++import qualified Burrito.Internal.Expand as Expand+import qualified Burrito.Internal.Match as Match+import qualified Burrito.Internal.Parse as Parse+import qualified Burrito.Internal.TH as TH+import qualified Burrito.Internal.Type.Template as Template+import qualified Burrito.Internal.Type.Value as Value+import qualified Data.Bifunctor as Bifunctor+import qualified Data.Map as Map+import qualified Data.Text as Text++-- | Constructs a string value.+stringValue :: String -> Value.Value+stringValue = Value.String . Text.pack++-- | Constructs a list value.+listValue :: [String] -> Value.Value+listValue = Value.List . fmap Text.pack++-- | Constructs a dictionary value.+dictionaryValue :: [(String, String)] -> Value.Value+dictionaryValue =+  Value.Dictionary . Map.fromList . fmap (Bifunctor.bimap Text.pack Text.pack)
+ source/library/Burrito/Internal/Expand.hs view
@@ -0,0 +1,312 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-}++module Burrito.Internal.Expand where++import qualified Burrito.Internal.Render as Render+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.Trans.Class as Trans+import qualified Control.Monad.Trans.State as State+import qualified Data.ByteString as ByteString+import qualified Data.Char as Char+import qualified Data.Functor.Identity as Identity+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.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Text.Lazy as LazyText+import qualified Data.Text.Lazy.Builder as Builder++-- | Expands a template using the given values. Unlike parsing, expansion+-- always succeeds. If no value is given for a variable, it will simply not+-- appear in the output.+--+-- >>> expand [] <$> parse "valid-template"+-- Just "valid-template"+-- >>> expand [] <$> parse "template:{example}"+-- Just "template:"+-- >>> expand [("example", stringValue "true")] <$> parse "template:{example}"+-- Just "template:true"+expand :: [(String, Value.Value)] -> Template.Template -> String+expand values =+  let m = Map.mapKeys Text.pack $ Map.fromList values+  in+    Render.builderToString . Identity.runIdentity . expandWith+      (pure . flip Map.lookup m)++-- | This is like @expand@ except that it gives you more control over how+-- variables are expanded. If you can, use @expand@. It's simpler.+--+-- Instead of passing in a static mapping form names to+-- values, you pass in a function that is used to look up values on the fly.+-- This can be useful if computing values takes a while or requires some impure+-- actions.+--+-- >>> expandWith (\ x -> [Nothing, Just . stringValue $ unpack x]) <$> parse "template:{example}"+-- Just ["template:","template:example"]+-- >>> let Just template = parse "user={USER}"+-- >>> expandWith (fmap (fmap stringValue) . lookupEnv . unpack) template+-- "user=taylor"+--+-- Note that as the RFC specifies, the given function will be called at most+-- once for each variable in the template.+--+-- >>> let Just template = parse "{a}{a}"+-- >>> expandWith (\ x -> do { putStrLn $ "-- expanding " <> show x; pure . Just $ Burrito.stringValue "A" }) template+-- -- expanding "a"+-- "AA"+expandWith+  :: Monad m+  => (Text.Text -> m (Maybe Value.Value))+  -> Template.Template+  -> m Builder.Builder+expandWith f = flip State.evalStateT Map.empty . template (cached f)++type CacheT = State.StateT (Map.Map Text.Text (Maybe Value.Value))++cached+  :: Monad m+  => (Text.Text -> m (Maybe Value.Value))+  -> Name.Name+  -> CacheT m (Maybe Value.Value)+cached f x = do+  let key = LazyText.toStrict . Builder.toLazyText $ name x+  cache <- State.get+  case Map.lookup key cache of+    Just result -> pure result+    Nothing -> do+      result <- Trans.lift $ f key+      State.modify $ Map.insert key result+      pure result++template+  :: Monad m+  => (Name.Name -> CacheT m (Maybe Value.Value))+  -> Template.Template+  -> CacheT m Builder.Builder+template f = fmap mconcat . traverse (token f) . Template.tokens++token+  :: Monad m+  => (Name.Name -> CacheT m (Maybe Value.Value))+  -> Token.Token+  -> CacheT m Builder.Builder+token f x = case x of+  Token.Expression y -> expression f y+  Token.Literal y -> pure $ literal y++expression+  :: Monad m+  => (Name.Name -> CacheT m (Maybe Value.Value))+  -> Expression.Expression+  -> CacheT m Builder.Builder+expression f ex =+  let op = Expression.operator ex+  in+    fmap+      (mconcat+      . (\xs -> if null xs then xs else prefix op : xs)+      . List.intersperse (separator op)+      . Maybe.catMaybes+      )+    . traverse (variable f op)+    . NonEmpty.toList+    $ Expression.variables ex++separator :: Operator.Operator -> Builder.Builder+separator op = Builder.singleton $ case op of+  Operator.Ampersand -> '&'+  Operator.FullStop -> '.'+  Operator.None -> ','+  Operator.NumberSign -> ','+  Operator.PlusSign -> ','+  Operator.QuestionMark -> '&'+  Operator.Semicolon -> ';'+  Operator.Solidus -> '/'++prefix :: Operator.Operator -> Builder.Builder+prefix op = case op of+  Operator.Ampersand -> Builder.singleton '&'+  Operator.FullStop -> Builder.singleton '.'+  Operator.None -> mempty+  Operator.NumberSign -> Builder.singleton '#'+  Operator.PlusSign -> mempty+  Operator.QuestionMark -> Builder.singleton '?'+  Operator.Semicolon -> Builder.singleton ';'+  Operator.Solidus -> Builder.singleton '/'++variable+  :: Monad m+  => (Name.Name -> CacheT m (Maybe Value.Value))+  -> Operator.Operator+  -> Variable.Variable+  -> CacheT m (Maybe Builder.Builder)+variable f op var = do+  res <- f $ Variable.name var+  pure $ case res of+    Nothing -> Nothing+    Just val -> value op var val++value+  :: Operator.Operator+  -> Variable.Variable+  -> Value.Value+  -> Maybe Builder.Builder+value op var val = case val of+  Value.Dictionary xs -> dictionaryValue op var $ Map.toAscList xs+  Value.List xs -> listValue op var xs+  Value.String x -> Just $ stringValue op var x++dictionaryValue+  :: Operator.Operator+  -> Variable.Variable+  -> [(Text.Text, Text.Text)]+  -> Maybe Builder.Builder+dictionaryValue = items $ \op var (k, v) ->+  let f = string op Modifier.None+  in+    case Variable.modifier var of+      Modifier.Asterisk -> [f k <> Builder.singleton '=' <> f v]+      _ -> [f k, f v]++listValue+  :: Operator.Operator+  -> Variable.Variable+  -> [Text.Text]+  -> Maybe Builder.Builder+listValue = items $ \op var -> pure . stringValue+  (case Variable.modifier var of+    Modifier.Asterisk -> op+    _ -> Operator.None+  )+  var { Variable.modifier = Modifier.None }++items+  :: (Operator.Operator -> Variable.Variable -> a -> [Builder.Builder])+  -> Operator.Operator+  -> Variable.Variable+  -> [a]+  -> Maybe Builder.Builder+items f op var xs =+  let+    md = Variable.modifier var+    sep = case md of+      Modifier.Asterisk -> separator op+      _ -> Builder.singleton ','+    p = case md of+      Modifier.Asterisk -> False+      _ -> case op of+        Operator.Ampersand -> True+        Operator.QuestionMark -> True+        Operator.Semicolon -> True+        _ -> False+  in if null xs+    then Nothing+    else+      Just+      . mconcat+      . (if p then (label True var :) else id)+      . List.intersperse sep+      $ concatMap (f op var) xs++label :: Bool -> Variable.Variable -> Builder.Builder+label p v =+  name (Variable.name v) <> if p then Builder.singleton '=' else mempty++name :: Name.Name -> Builder.Builder+name =+  mconcat+    . List.intersperse (Builder.singleton '.')+    . fmap field+    . NonEmpty.toList+    . Name.fields++field :: Field.Field -> Builder.Builder+field = foldMap (character $ const True) . Field.characters++character :: (Char -> Bool) -> Character.Character tag -> Builder.Builder+character f x = case x of+  Character.Encoded y z -> Render.encodedCharacter y z+  Character.Unencoded y -> unencodedCharacter f y++stringValue+  :: Operator.Operator -> Variable.Variable -> Text.Text -> Builder.Builder+stringValue op var str =+  let+    pre = case op of+      Operator.Ampersand -> label True var+      Operator.QuestionMark -> label True var+      Operator.Semicolon -> label (not $ Text.null str) var+      _ -> mempty+  in pre <> string op (Variable.modifier var) str++string+  :: Operator.Operator -> Modifier.Modifier -> Text.Text -> Builder.Builder+string op md =+  let+    allowed x = case op of+      Operator.NumberSign -> isAllowed x+      Operator.PlusSign -> isAllowed x+      _ -> isUnreserved x+    trim = case md of+      Modifier.Colon ml -> Text.take $ MaxLength.count ml+      _ -> id+  in foldMap (unencodedCharacter allowed) . Text.unpack . trim++isAllowed :: Char -> Bool+isAllowed x = isUnreserved x || isReserved x++isUnreserved :: Char -> Bool+isUnreserved x = case x of+  '-' -> True+  '.' -> True+  '_' -> True+  '~' -> True+  _ -> Char.isAsciiUpper x || Char.isAsciiLower x || Char.isDigit x++isReserved :: Char -> Bool+isReserved x = case x of+  '!' -> True+  '$' -> True+  '&' -> True+  '\'' -> True+  '(' -> True+  ')' -> True+  '*' -> True+  '+' -> True+  ',' -> True+  ';' -> True+  '=' -> True+  ':' -> True+  '/' -> True+  '?' -> True+  '#' -> True+  '[' -> True+  ']' -> True+  '@' -> True+  _ -> False++unencodedCharacter :: (Char -> Bool) -> Char -> Builder.Builder+unencodedCharacter f x = if f x+  then Builder.singleton x+  else foldMap (uncurry Render.encodedCharacter) $ encodeCharacter x++encodeCharacter :: Char -> [(Digit.Digit, Digit.Digit)]+encodeCharacter =+  fmap Digit.fromWord8 . ByteString.unpack . Text.encodeUtf8 . Text.singleton++literal :: Literal.Literal -> Builder.Builder+literal = foldMap (character isAllowed) . Literal.characters
+ source/library/Burrito/Internal/Match.hs view
@@ -0,0 +1,273 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-}++module Burrito.Internal.Match where++import qualified Burrito.Internal.Expand as Expand+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.Literal as Literal+import qualified Burrito.Internal.Type.Match as Match+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.ByteString as ByteString+import qualified Data.Char as Char+import qualified Data.List as List+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Maybe as Maybe+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Text.ParserCombinators.ReadP as ReadP++-- | Matches a string against a template. This is essentially the opposite of+-- @expand@.+--+-- Since there isn't always one unique match, this function returns all the+-- possibilities. It's up to you to select the one that makes the most sense,+-- or to simply grab the first one if you don't care.+--+-- >>> match "" <$> parse "no-match"+-- Just []+-- >>> match "no-variables" <$> parse "no-variables"+-- Just [[]]+-- >>> match "1-match" <$> parse "{one}-match"+-- Just [[("one",String "1")]]+--+-- Be warned that the number of possible matches can grow quickly if your+-- template has variables next to each other without any separators.+--+-- >>> let Just template = parse "{a}{b}"+-- >>> mapM_ print $ match "ab" template+-- [("a",String "a"),("b",String "b")]+-- [("a",String "ab"),("b",String "")]+-- [("a",String "ab")]+-- [("a",String ""),("b",String "ab")]+-- [("b",String "ab")]+--+-- Matching supports everything /except/ explode modifiers (@{a*}@), list+-- values, and dictionary values.+match :: String -> Template.Template -> [[(String, Value.Value)]]+match s =+  fmap finalize+    . Maybe.mapMaybe (keepConsistent . fst)+    . flip ReadP.readP_to_S s+    . template++finalize :: [(Name.Name, Match.Match)] -> [(String, Value.Value)]+finalize = Maybe.mapMaybe $ \(n, m) -> case m of+  Match.Defined v ->+    Just (Render.builderToString $ Render.name n, Value.String v)+  Match.Prefix _ v ->+    Just (Render.builderToString $ Render.name n, Value.String v)+  Match.Undefined -> Nothing++keepConsistent+  :: [(Name.Name, Match.Match)] -> Maybe [(Name.Name, Match.Match)]+keepConsistent xs = case xs of+  [] -> Just xs+  (k, v) : ys -> do+    let (ts, fs) = List.partition ((== k) . fst) ys+    w <- combine v $ fmap snd ts+    ((k, w) :) <$> keepConsistent fs++combine :: Match.Match -> [Match.Match] -> Maybe Match.Match+combine x ys = case ys of+  [] -> Just x+  y : zs -> case x of+    Match.Defined t -> case y of+      Match.Defined u | t == u -> combine x zs+      Match.Prefix m u | Text.take (MaxLength.count m) t == u -> combine x zs+      _ -> Nothing+    Match.Prefix n t -> case y of+      Match.Defined u | t == Text.take (MaxLength.count n) u -> combine y zs+      Match.Prefix m u+        | let c = MaxLength.count (min n m) in Text.take c t == Text.take c u+        -> combine (if m > n then y else x) zs+      _ -> Nothing+    Match.Undefined -> case y of+      Match.Undefined -> combine x zs+      _ -> Nothing++template :: Template.Template -> ReadP.ReadP [(Name.Name, Match.Match)]+template x = do+  xs <- fmap mconcat . traverse token $ Template.tokens x+  ReadP.eof+  pure xs++token :: Token.Token -> ReadP.ReadP [(Name.Name, Match.Match)]+token x = case x of+  Token.Expression y -> expression y+  Token.Literal y -> [] <$ literal y++expression :: Expression.Expression -> ReadP.ReadP [(Name.Name, Match.Match)]+expression x = variables (Expression.operator x) (Expression.variables x)++variables+  :: Operator.Operator+  -> NonEmpty.NonEmpty Variable.Variable+  -> ReadP.ReadP [(Name.Name, Match.Match)]+variables op vs = case op of+  Operator.Ampersand -> vars vs (Just '&') '&' varEq+  Operator.FullStop -> vars vs (Just '.') '.' $ variable Expand.isUnreserved+  Operator.None -> vars vs Nothing ',' $ variable Expand.isUnreserved+  Operator.NumberSign -> vars vs (Just '#') ',' $ variable Expand.isAllowed+  Operator.PlusSign -> vars vs Nothing ',' $ variable Expand.isAllowed+  Operator.QuestionMark -> vars vs (Just '?') '&' varEq+  Operator.Semicolon -> vars vs (Just ';') ';' $ \v -> do+    let n = Variable.name v+    name n+    ReadP.option [(n, Match.Defined Text.empty)] $ do+      char_ '='+      variable Expand.isUnreserved v+  Operator.Solidus -> vars vs (Just '/') '/' $ variable Expand.isUnreserved++vars+  :: NonEmpty.NonEmpty Variable.Variable+  -> Maybe Char+  -> Char+  -> (Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)])+  -> ReadP.ReadP [(Name.Name, Match.Match)]+vars vs m c f = do+  let+    ctx = case m of+      Nothing -> id+      Just o -> \p -> ReadP.option (undef <$> NonEmpty.toList vs) $ do+        char_ o+        xs <- p+        Monad.guard . not $ all isUndefined xs+        pure xs+  ctx . vars' c f $ NonEmpty.toList vs++isUndefined :: (Name.Name, Match.Match) -> Bool+isUndefined = (== Match.Undefined) . snd++vars'+  :: Char+  -> (Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)])+  -> [Variable.Variable]+  -> ReadP.ReadP [(Name.Name, Match.Match)]+vars' c f vs = case vs of+  [] -> pure []+  v : ws ->+    let+      this = do+        x <- f v+        xs <- ReadP.option (undef <$> ws) $ do+          char_ c+          vars' c f ws+        pure $ x <> xs+      that = (undef v :) <$> vars' c f ws+    in this ReadP.+++ that++undef :: Variable.Variable -> (Name.Name, Match.Match)+undef v = (Variable.name v, Match.Undefined)++char_ :: Char -> ReadP.ReadP ()+char_ = Monad.void . ReadP.char++varEq :: Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)]+varEq v = do+  name $ Variable.name v+  char_ '='+  variable Expand.isUnreserved v++name :: Name.Name -> ReadP.ReadP ()+name = Monad.void . ReadP.string . Render.builderToString . Render.name++variable+  :: (Char -> Bool)+  -> Variable.Variable+  -> ReadP.ReadP [(Name.Name, Match.Match)]+variable f x = do+  v <- case Variable.modifier x of+    Modifier.Asterisk -> ReadP.pfail+    Modifier.None -> Match.Defined <$> manyCharacters f+    Modifier.Colon n -> Match.Prefix n <$> manyCharacters f+  pure [(Variable.name x, v)]++manyCharacters :: (Char -> Bool) -> ReadP.ReadP Text.Text+manyCharacters f = do+  let+    f1 = (:) <$> someEncodedCharacters <*> ReadP.option [] f2+    f2 = (:) <$> someUnencodedCharacters f <*> ReadP.option [] f1+  fmap mconcat . ReadP.option [] $ f1 ReadP.<++ f2++someEncodedCharacters :: ReadP.ReadP Text.Text+someEncodedCharacters = do+  xs <- some anEncodedCharacter+  either (fail . show) pure+    . Text.decodeUtf8'+    . ByteString.pack+    . fmap (uncurry Digit.toWord8)+    $ NonEmpty.toList xs++some :: ReadP.ReadP a -> ReadP.ReadP (NonEmpty.NonEmpty a)+some p = (NonEmpty.:|) <$> p <*> ReadP.many p++someUnencodedCharacters :: (Char -> Bool) -> ReadP.ReadP Text.Text+someUnencodedCharacters f = do+  xs <- some $ ReadP.satisfy f+  pure . Text.pack $ NonEmpty.toList xs++anEncodedCharacter :: ReadP.ReadP (Digit.Digit, Digit.Digit)+anEncodedCharacter = do+  char_ '%'+  (,) <$> aDigit <*> aDigit++aDigit :: ReadP.ReadP Digit.Digit+aDigit = do+  x <- ReadP.satisfy Char.isHexDigit+  maybe (fail "invalid Digit") pure $ Digit.fromChar x++literal :: Literal.Literal -> ReadP.ReadP ()+literal = mapM_ literalCharacter . Literal.characters++literalCharacter :: Character.Character Literal.Literal -> ReadP.ReadP ()+literalCharacter = character Expand.isAllowed++character :: (Char -> Bool) -> Character.Character tag -> ReadP.ReadP ()+character f x = case x of+  Character.Encoded y z -> encodedCharacter y z+  Character.Unencoded y -> unencodedCharacter f y++encodedCharacter :: Digit.Digit -> Digit.Digit -> ReadP.ReadP ()+encodedCharacter x y = char_ '%' *> digit x *> digit y++digit :: Digit.Digit -> ReadP.ReadP ()+digit x = char_ $ case x of+  Digit.Ox0 -> '0'+  Digit.Ox1 -> '1'+  Digit.Ox2 -> '2'+  Digit.Ox3 -> '3'+  Digit.Ox4 -> '4'+  Digit.Ox5 -> '5'+  Digit.Ox6 -> '6'+  Digit.Ox7 -> '7'+  Digit.Ox8 -> '8'+  Digit.Ox9 -> '9'+  Digit.OxA Case.Upper -> 'A'+  Digit.OxB Case.Upper -> 'B'+  Digit.OxC Case.Upper -> 'C'+  Digit.OxD Case.Upper -> 'D'+  Digit.OxE Case.Upper -> 'E'+  Digit.OxF Case.Upper -> 'F'+  Digit.OxA Case.Lower -> 'a'+  Digit.OxB Case.Lower -> 'b'+  Digit.OxC Case.Lower -> 'c'+  Digit.OxD Case.Lower -> 'd'+  Digit.OxE Case.Lower -> 'e'+  Digit.OxF Case.Lower -> 'f'++unencodedCharacter :: (Char -> Bool) -> Char -> ReadP.ReadP ()+unencodedCharacter f x = if f x+  then char_ x+  else mapM_ (uncurry encodedCharacter) $ Expand.encodeCharacter x
+ source/library/Burrito/Internal/Parse.hs view
@@ -0,0 +1,176 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-}++{-# LANGUAGE FlexibleContexts #-}++module Burrito.Internal.Parse where++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.Variable as Variable+import qualified Data.Char as Char+import qualified Data.Ix as Ix+import qualified Data.List.NonEmpty as NonEmpty+import qualified Text.Parsec as Parsec+import qualified Text.Read as Read++-- | Attempts to parse a string as a URI template. If parsing fails, this will+-- return @Nothing@. Otherwise it will return @Just@ the parsed template.+--+-- Parsing will usually succeed, but it can fail if the input string contains+-- characters that are not valid in IRIs (like @^@) or if the input string+-- contains an invalid template expression (like @{!}@). To include characters+-- that aren't valid in IRIs, percent encode them (like @%5E@).+--+-- >>> parse "invalid template"+-- Nothing+-- >>> parse "valid-template"+-- Just (Template ...)+parse :: String -> Maybe Template.Template+parse = either (const Nothing) Just . Parsec.parse template ""++template :: Parsec.Stream s m Char => Parsec.ParsecT s u m Template.Template+template = Template.Template <$> Parsec.many token <* Parsec.eof++token :: Parsec.Stream s m Char => Parsec.ParsecT s u m Token.Token+token = choice (Token.Expression <$> expression) (Token.Literal <$> literal)++choice+  :: Parsec.ParsecT s u m a -> Parsec.ParsecT s u m a -> Parsec.ParsecT s u m a+choice = (Parsec.<|>)++expression+  :: Parsec.Stream s m Char => Parsec.ParsecT s u m Expression.Expression+expression =+  Parsec.between (Parsec.char '{') (Parsec.char '}')+    $ Expression.Expression+    <$> operator+    <*> sepBy1 variable (Parsec.char ',')++operator :: Parsec.Stream s m Char => Parsec.ParsecT s u m Operator.Operator+operator = Parsec.option Operator.None $ Parsec.choice+  [ Operator.Ampersand <$ Parsec.char '&'+  , Operator.FullStop <$ Parsec.char '.'+  , Operator.NumberSign <$ Parsec.char '#'+  , Operator.PlusSign <$ Parsec.char '+'+  , Operator.QuestionMark <$ Parsec.char '?'+  , Operator.Semicolon <$ Parsec.char ';'+  , Operator.Solidus <$ Parsec.char '/'+  ]++sepBy1+  :: Parsec.ParsecT s u m a+  -> Parsec.ParsecT s u m x+  -> Parsec.ParsecT s u m (NonEmpty.NonEmpty a)+sepBy1 p s = (NonEmpty.:|) <$> p <*> Parsec.many (s *> p)++variable :: Parsec.Stream s m Char => Parsec.ParsecT s u m Variable.Variable+variable = Variable.Variable <$> name <*> modifier++name :: Parsec.Stream s m Char => Parsec.ParsecT s u m Name.Name+name = Name.Name <$> sepBy1 field (Parsec.char '.')++field :: Parsec.Stream s m Char => Parsec.ParsecT s u m Field.Field+field = Field.Field <$> nonEmpty fieldCharacter++nonEmpty+  :: Parsec.ParsecT s u m a -> Parsec.ParsecT s u m (NonEmpty.NonEmpty a)+nonEmpty p = (NonEmpty.:|) <$> p <*> Parsec.many p++fieldCharacter+  :: Parsec.Stream s m Char+  => Parsec.ParsecT s u m (Character.Character Field.Field)+fieldCharacter = choice encodedCharacter (unencodedCharacter isFieldCharacter)++encodedCharacter+  :: Parsec.Stream s m Char => Parsec.ParsecT s u m (Character.Character tag)+encodedCharacter = Parsec.char '%' >> Character.Encoded <$> digit <*> digit++digit :: Parsec.Stream s m Char => Parsec.ParsecT s u m Digit.Digit+digit = do+  x <- Parsec.satisfy Char.isHexDigit+  maybe (fail "invalid Digit") pure $ Digit.fromChar x++unencodedCharacter+  :: Parsec.Stream s m Char+  => (Char -> Bool)+  -> Parsec.ParsecT s u m (Character.Character tag)+unencodedCharacter = fmap Character.Unencoded . Parsec.satisfy++isFieldCharacter :: Char -> Bool+isFieldCharacter x = case x of+  '_' -> True+  _ -> Char.isAsciiUpper x || Char.isAsciiLower x || Char.isDigit x++modifier :: Parsec.Stream s m Char => Parsec.ParsecT s u m Modifier.Modifier+modifier = Parsec.option Modifier.None $ Parsec.choice+  [ Modifier.Asterisk <$ Parsec.char '*'+  , Parsec.char ':' >> Modifier.Colon <$> maxLength+  ]++maxLength+  :: Parsec.Stream s m Char => Parsec.ParsecT s u m MaxLength.MaxLength+maxLength = do+  x <- Parsec.satisfy $ Ix.inRange ('1', '9')+  xs <- Parsec.many $ Parsec.satisfy Char.isDigit+  n <- maybe (fail "invalid MaxLength") pure . Read.readMaybe $ x : xs+  if isMaxLength n+    then pure $ MaxLength.MaxLength n+    else fail "invalid MaxLength"++isMaxLength :: Int -> Bool+isMaxLength = Ix.inRange (1, 9999)++literal :: Parsec.Stream s m Char => Parsec.ParsecT s u m Literal.Literal+literal = Literal.Literal <$> nonEmpty literalCharacter++literalCharacter+  :: Parsec.Stream s m Char+  => Parsec.ParsecT s u m (Character.Character Literal.Literal)+literalCharacter =+  choice encodedCharacter (unencodedCharacter isLiteralCharacter)++isLiteralCharacter :: Char -> Bool+isLiteralCharacter x = case x of+  ' ' -> False+  '"' -> False+  '\'' -> False+  '%' -> False+  '<' -> False+  '>' -> False+  '\\' -> False+  '^' -> False+  '`' -> False+  '{' -> False+  '|' -> False+  '}' -> False+  _ ->+    Ix.inRange ('\x20', '\x7e') x+      || Ix.inRange ('\xa0', '\xd7ff') x+      || Ix.inRange ('\xe000', '\xf8ff') x+      || Ix.inRange ('\xf900', '\xfdcf') x+      || Ix.inRange ('\xfdf0', '\xffef') x+      || Ix.inRange ('\x10000', '\x1fffd') x+      || Ix.inRange ('\x20000', '\x2fffd') x+      || Ix.inRange ('\x30000', '\x3fffd') x+      || Ix.inRange ('\x40000', '\x4fffd') x+      || Ix.inRange ('\x50000', '\x5fffd') x+      || Ix.inRange ('\x60000', '\x6fffd') x+      || Ix.inRange ('\x70000', '\x7fffd') x+      || Ix.inRange ('\x80000', '\x8fffd') x+      || Ix.inRange ('\x90000', '\x9fffd') x+      || Ix.inRange ('\xa0000', '\xafffd') x+      || Ix.inRange ('\xb0000', '\xbfffd') x+      || Ix.inRange ('\xc0000', '\xcfffd') x+      || Ix.inRange ('\xd0000', '\xdfffd') x+      || Ix.inRange ('\xe1000', '\xefffd') x+      || Ix.inRange ('\xf0000', '\xffffd') x+      || Ix.inRange ('\x100000', '\x10fffd') x
+ source/library/Burrito/Internal/Render.hs view
@@ -0,0 +1,107 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-}++module Burrito.Internal.Render where++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.Token as Token+import qualified Burrito.Internal.Type.Variable as Variable+import qualified Data.List as List+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Text.Lazy as LazyText+import qualified Data.Text.Lazy.Builder as Builder++builderToString :: Builder.Builder -> String+builderToString = LazyText.unpack . Builder.toLazyText++token :: Token.Token -> Builder.Builder+token x = case x of+  Token.Expression y -> expression y+  Token.Literal y -> literal y++expression :: Expression.Expression -> Builder.Builder+expression x =+  Builder.singleton '{'+    <> operator (Expression.operator x)+    <> sepBy1 variable (Builder.singleton ',') (Expression.variables x)+    <> Builder.singleton '}'++operator :: Operator.Operator -> Builder.Builder+operator x = case x of+  Operator.Ampersand -> Builder.singleton '&'+  Operator.FullStop -> Builder.singleton '.'+  Operator.None -> mempty+  Operator.NumberSign -> Builder.singleton '#'+  Operator.PlusSign -> Builder.singleton '+'+  Operator.QuestionMark -> Builder.singleton '?'+  Operator.Semicolon -> Builder.singleton ';'+  Operator.Solidus -> Builder.singleton '/'++sepBy1+  :: (a -> Builder.Builder)+  -> Builder.Builder+  -> NonEmpty.NonEmpty a+  -> Builder.Builder+sepBy1 f x = mconcat . List.intersperse x . fmap f . NonEmpty.toList++variable :: Variable.Variable -> Builder.Builder+variable x = name (Variable.name x) <> modifier (Variable.modifier x)++name :: Name.Name -> Builder.Builder+name = sepBy1 field (Builder.singleton '.') . Name.fields++field :: Field.Field -> Builder.Builder+field = foldMap character . Field.characters++character :: Character.Character tag -> Builder.Builder+character x = case x of+  Character.Encoded y z -> encodedCharacter y z+  Character.Unencoded y -> Builder.singleton y++encodedCharacter :: Digit.Digit -> Digit.Digit -> Builder.Builder+encodedCharacter x y = Builder.singleton '%' <> digit x <> digit y++digit :: Digit.Digit -> Builder.Builder+digit x = Builder.singleton $ case x of+  Digit.Ox0 -> '0'+  Digit.Ox1 -> '1'+  Digit.Ox2 -> '2'+  Digit.Ox3 -> '3'+  Digit.Ox4 -> '4'+  Digit.Ox5 -> '5'+  Digit.Ox6 -> '6'+  Digit.Ox7 -> '7'+  Digit.Ox8 -> '8'+  Digit.Ox9 -> '9'+  Digit.OxA Case.Upper -> 'A'+  Digit.OxB Case.Upper -> 'B'+  Digit.OxC Case.Upper -> 'C'+  Digit.OxD Case.Upper -> 'D'+  Digit.OxE Case.Upper -> 'E'+  Digit.OxF Case.Upper -> 'F'+  Digit.OxA Case.Lower -> 'a'+  Digit.OxB Case.Lower -> 'b'+  Digit.OxC Case.Lower -> 'c'+  Digit.OxD Case.Lower -> 'd'+  Digit.OxE Case.Lower -> 'e'+  Digit.OxF Case.Lower -> 'f'++modifier :: Modifier.Modifier -> Builder.Builder+modifier x = case x of+  Modifier.Asterisk -> Builder.singleton '*'+  Modifier.Colon y -> Builder.singleton ':' <> maxLength y+  Modifier.None -> mempty++maxLength :: MaxLength.MaxLength -> Builder.Builder+maxLength = Builder.fromString . show . MaxLength.count++literal :: Literal.Literal -> Builder.Builder+literal = foldMap character . Literal.characters
+ source/library/Burrito/Internal/TH.hs view
@@ -0,0 +1,30 @@+module Burrito.Internal.TH+  ( uriTemplate+  ) where++import qualified Burrito.Internal.Parse as Parse+import qualified Language.Haskell.TH.Quote as TH+import qualified Language.Haskell.TH.Syntax as TH++-- | This can be used together with the @QuasiQuotes@ language extension to+-- parse a URI template at compile time. This is convenient because it allows+-- you to verify the validity of the template when you compile your file as+-- opposed to when you run it.+--+-- >>> :set -XQuasiQuotes+-- >>> import Burrito+-- >>> let template = [uriTemplate|http://example/search{?query}|]+-- >>> let values = [("query", stringValue "chorizo")]+-- >>> expand values template+-- "http://example/search?query=chorizo"+--+-- Note that you cannot use escape sequences in this quasi-quoter. For example,+-- this is invalid: @[uriTemplate|\\xa0|]@. You can however use percent encoded+-- triples as normal. So this is valid: @[uriTemplate|%c2%a0|]@.+uriTemplate :: TH.QuasiQuoter+uriTemplate = TH.QuasiQuoter+  { TH.quoteDec = const $ fail "cannot be used as a declaration"+  , TH.quoteExp = maybe (fail "invalid URI template") TH.liftData . Parse.parse+  , TH.quotePat = const $ fail "cannot be used as a pattern"+  , TH.quoteType = const $ fail "cannot be used as a type"+  }
+ source/library/Burrito/Internal/Type/Case.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Case+  ( Case(..)+  ) where++import qualified Data.Data as Data++data Case+  = Lower+  | Upper+  deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Character.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Character+  ( Character(..)+  ) where++import qualified Burrito.Internal.Type.Digit as Digit+import qualified Data.Data as Data++data Character tag+  = Encoded Digit.Digit Digit.Digit+  | Unencoded Char+  deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Digit.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Digit+  ( Digit(..)+  , fromChar+  , fromWord8+  , toWord8+  ) where++import qualified Burrito.Internal.Type.Case as Case+import qualified Data.Bits as Bits+import qualified Data.Data as Data+import qualified Data.Word as Word++data Digit+  = Ox0+  | Ox1+  | Ox2+  | Ox3+  | Ox4+  | Ox5+  | Ox6+  | Ox7+  | Ox8+  | Ox9+  | OxA Case.Case+  | OxB Case.Case+  | OxC Case.Case+  | OxD Case.Case+  | OxE Case.Case+  | OxF Case.Case+  deriving (Data.Data, Eq, Ord, Show)++fromChar :: Char -> Maybe Digit+fromChar x = case x of+  '0' -> Just Ox0+  '1' -> Just Ox1+  '2' -> Just Ox2+  '3' -> Just Ox3+  '4' -> Just Ox4+  '5' -> Just Ox5+  '6' -> Just Ox6+  '7' -> Just Ox7+  '8' -> Just Ox8+  '9' -> Just Ox9+  'A' -> Just $ OxA Case.Upper+  'B' -> Just $ OxB Case.Upper+  'C' -> Just $ OxC Case.Upper+  'D' -> Just $ OxD Case.Upper+  'E' -> Just $ OxE Case.Upper+  'F' -> Just $ OxF Case.Upper+  'a' -> Just $ OxA Case.Lower+  'b' -> Just $ OxB Case.Lower+  'c' -> Just $ OxC Case.Lower+  'd' -> Just $ OxD Case.Lower+  'e' -> Just $ OxE Case.Lower+  'f' -> Just $ OxF Case.Lower+  _ -> Nothing++fromWord8 :: Word.Word8 -> (Digit, Digit)+fromWord8 x =+  let+    f :: Word.Word8 -> Digit+    f y = case y of+      0x0 -> Ox0+      0x1 -> Ox1+      0x2 -> Ox2+      0x3 -> Ox3+      0x4 -> Ox4+      0x5 -> Ox5+      0x6 -> Ox6+      0x7 -> Ox7+      0x8 -> Ox8+      0x9 -> Ox9+      0xA -> OxA Case.Upper+      0xB -> OxB Case.Upper+      0xC -> OxC Case.Upper+      0xD -> OxD Case.Upper+      0xE -> OxE Case.Upper+      0xF -> OxF Case.Upper+      _ -> error $ "invalid nibble: " <> show y+  in (f $ Bits.shiftR x 4, f $ x Bits..&. 0x0F)++toWord8 :: Digit -> Digit -> Word.Word8+toWord8 x y =+  let+    f :: Digit -> Word.Word8+    f z = case z of+      Ox0 -> 0x0+      Ox1 -> 0x1+      Ox2 -> 0x2+      Ox3 -> 0x3+      Ox4 -> 0x4+      Ox5 -> 0x5+      Ox6 -> 0x6+      Ox7 -> 0x7+      Ox8 -> 0x8+      Ox9 -> 0x9+      OxA _ -> 0xA+      OxB _ -> 0xB+      OxC _ -> 0xC+      OxD _ -> 0xD+      OxE _ -> 0xE+      OxF _ -> 0xF+  in Bits.shiftL (f x) 4 Bits..|. f y
+ source/library/Burrito/Internal/Type/Expression.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Expression+  ( Expression(..)+  ) where++import qualified Burrito.Internal.Type.Operator as Operator+import qualified Burrito.Internal.Type.Variable as Variable+import qualified Data.Data as Data+import qualified Data.List.NonEmpty as NonEmpty++data Expression = Expression+  { operator :: Operator.Operator+  , variables :: NonEmpty.NonEmpty Variable.Variable+  }+  deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Field.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Field+  ( Field(..)+  ) where++import qualified Burrito.Internal.Type.Character as Character+import qualified Data.Data as Data+import qualified Data.List.NonEmpty as NonEmpty++newtype Field = Field+  { characters :: NonEmpty.NonEmpty (Character.Character Field)+  } deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Literal.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Literal+  ( Literal(..)+  ) where++import qualified Burrito.Internal.Type.Character as Character+import qualified Data.Data as Data+import qualified Data.List.NonEmpty as NonEmpty++newtype Literal = Literal+  { characters :: NonEmpty.NonEmpty (Character.Character Literal)+  } deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Match.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Match+  ( Match(..)+  ) where++import qualified Burrito.Internal.Type.MaxLength as MaxLength+import qualified Data.Data as Data+import qualified Data.Text as Text++data Match+  = Defined Text.Text+  | Prefix MaxLength.MaxLength Text.Text+  | Undefined+  deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/MaxLength.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.MaxLength+  ( MaxLength(..)+  ) where++import qualified Data.Data as Data++newtype MaxLength = MaxLength+  { count :: Int+  } deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Modifier.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Modifier+  ( Modifier(..)+  ) where++import qualified Burrito.Internal.Type.MaxLength as MaxLength+import qualified Data.Data as Data++data Modifier+  = Asterisk+  | Colon MaxLength.MaxLength+  | None+  deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Name.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Name+  ( Name(..)+  ) where++import qualified Burrito.Internal.Type.Field as Field+import qualified Data.Data as Data+import qualified Data.List.NonEmpty as NonEmpty++newtype Name = Name+  { fields :: NonEmpty.NonEmpty Field.Field+  } deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Operator.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Operator+  ( Operator(..)+  ) where++import qualified Data.Data as Data++data Operator+  = Ampersand+  | FullStop+  | None+  | NumberSign+  | PlusSign+  | QuestionMark+  | Semicolon+  | Solidus+  deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Template.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Template+  ( Template(..)+  , render+  ) where++import qualified Burrito.Internal.Render as Render+import qualified Burrito.Internal.Type.Token as Token+import qualified Data.Data as Data+import qualified Data.Text.Lazy.Builder as Builder++-- | Represents a URI template.+newtype Template = Template+  { tokens :: [Token.Token]+  } deriving (Data.Data, Eq, Ord)++instance Show Template where+  show = render++-- | Renders a template back into a string. This is essentially the opposite of+-- @parse@. Usually you'll want to use @expand@ to actually substitute+-- variables in the template, but this can be useful for printing out the+-- template itself+--+-- >>> render <$> parse "valid-template"+-- Just "valid-template"+-- >>> render <$> parse "{var}"+-- Just "{var}"+render :: Template -> String+render = Render.builderToString . template++template :: Template -> Builder.Builder+template = foldMap Render.token . tokens
+ source/library/Burrito/Internal/Type/Token.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Token+  ( Token(..)+  ) where++import qualified Burrito.Internal.Type.Expression as Expression+import qualified Burrito.Internal.Type.Literal as Literal+import qualified Data.Data as Data++data Token+  = Expression Expression.Expression+  | Literal Literal.Literal+  deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Value.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Value+  ( Value(..)+  ) where++import qualified Data.Data as Data+import qualified Data.Map as Map+import qualified Data.Text as Text++-- | Represents a value that can be substituted into a template. Can be a+-- string, a list, or dictionary (which is called an associative array in the+-- RFC).+data Value+  = Dictionary (Map.Map Text.Text Text.Text)+  | List [Text.Text]+  | String Text.Text+  deriving (Data.Data, Eq, Ord, Show)
+ source/library/Burrito/Internal/Type/Variable.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Burrito.Internal.Type.Variable+  ( Variable(..)+  ) where++import qualified Burrito.Internal.Type.Modifier as Modifier+import qualified Burrito.Internal.Type.Name as Name+import qualified Data.Data as Data++data Variable = Variable+  { name :: Name.Name+  , modifier :: Modifier.Modifier+  }+  deriving (Data.Data, Eq, Ord, Show)
+ source/test-suite/Main.hs view
@@ -0,0 +1,1470 @@+{-# OPTIONS_GHC -Wno-monomorphism-restriction #-}++{-# 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 (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
− src/lib/Burrito.hs
@@ -1,65 +0,0 @@--- | Burrito is a Haskell library for parsing and rendering URI templates.------ According to [RFC 6570](https://tools.ietf.org/html/rfc6570): "A URI--- Template is a compact sequence of characters for describing a range of-  -- Uniform Resource Identifiers through variable expansion." Burrito-  -- implements URI templates according to the specification in that RFC.------ The term "uniform resource identifiers" (URI) is often used interchangeably--- with other related terms like "internationalized resource identifier" (IRI),--- "uniform resource locator" (URL), and "uniform resource name" (URN). Burrito--- can be used for all of these. If you want to get technical, its input must--- be a valid IRI and its output will be a valid URI or URN.------ Although Burrito is primarily intended to be used with HTTP and HTTPS URIs,--- it should work with other schemes as well.------ If you're not already familiar with URI templates, I recommend reading the--- overview of the RFC. It's short, to the point, and easy to understand.------ Assuming you're familiar with URI templates, here's a simple example to show--- you how Burrito works:------ >>> import Burrito--- >>> let Just template = parse "http://example/search{?query}"--- >>> expand [("query", stringValue "chorizo")] template--- "http://example.com/search?query=chorizo"------ In short, use @parse@ to parse templates and @expand@ to render them.-module Burrito-  ( Parse.parse-  , Template.render-  , Expand.expand-  , Expand.expandWith-  , Match.match-  , TH.uriTemplate-  , Template.Template-  , Value.Value-  , stringValue-  , listValue-  , dictionaryValue-  )-where--import qualified Burrito.Internal.Expand as Expand-import qualified Burrito.Internal.Match as Match-import qualified Burrito.Internal.Parse as Parse-import qualified Burrito.Internal.TH as TH-import qualified Burrito.Internal.Type.Template as Template-import qualified Burrito.Internal.Type.Value as Value-import qualified Data.Bifunctor as Bifunctor-import qualified Data.Map as Map-import qualified Data.Text as Text---- | Constructs a string value.-stringValue :: String -> Value.Value-stringValue = Value.String . Text.pack---- | Constructs a list value.-listValue :: [String] -> Value.Value-listValue = Value.List . fmap Text.pack---- | Constructs a dictionary value.-dictionaryValue :: [(String, String)] -> Value.Value-dictionaryValue =-  Value.Dictionary . Map.fromList . fmap (Bifunctor.bimap Text.pack Text.pack)
− src/lib/Burrito/Internal/Expand.hs
@@ -1,312 +0,0 @@-{-# OPTIONS_GHC -Wno-missing-export-lists #-}--module Burrito.Internal.Expand where--import qualified Burrito.Internal.Render as Render-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.Trans.Class as Trans-import qualified Control.Monad.Trans.State as State-import qualified Data.ByteString as ByteString-import qualified Data.Char as Char-import qualified Data.Functor.Identity as Identity-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.Text as Text-import qualified Data.Text.Encoding as Text-import qualified Data.Text.Lazy as LazyText-import qualified Data.Text.Lazy.Builder as Builder---- | Expands a template using the given values. Unlike parsing, expansion--- always succeeds. If no value is given for a variable, it will simply not--- appear in the output.------ >>> expand [] <$> parse "valid-template"--- Just "valid-template"--- >>> expand [] <$> parse "template:{example}"--- Just "template:"--- >>> expand [("example", stringValue "true")] <$> parse "template:{example}"--- Just "template:true"-expand :: [(String, Value.Value)] -> Template.Template -> String-expand values =-  let m = Map.mapKeys Text.pack $ Map.fromList values-  in-    Render.builderToString . Identity.runIdentity . expandWith-      (pure . flip Map.lookup m)---- | This is like @expand@ except that it gives you more control over how--- variables are expanded. If you can, use @expand@. It's simpler.------ Instead of passing in a static mapping form names to--- values, you pass in a function that is used to look up values on the fly.--- This can be useful if computing values takes a while or requires some impure--- actions.------ >>> expandWith (\ x -> [Nothing, Just . stringValue $ unpack x]) <$> parse "template:{example}"--- Just ["template:","template:example"]--- >>> let Just template = parse "user={USER}"--- >>> expandWith (fmap (fmap stringValue) . lookupEnv . unpack) template--- "user=taylor"------ Note that as the RFC specifies, the given function will be called at most--- once for each variable in the template.------ >>> let Just template = parse "{a}{a}"--- >>> expandWith (\ x -> do { putStrLn $ "-- expanding " <> show x; pure . Just $ Burrito.stringValue "A" }) template--- -- expanding "a"--- "AA"-expandWith-  :: Monad m-  => (Text.Text -> m (Maybe Value.Value))-  -> Template.Template-  -> m Builder.Builder-expandWith f = flip State.evalStateT Map.empty . template (cached f)--type CacheT = State.StateT (Map.Map Text.Text (Maybe Value.Value))--cached-  :: Monad m-  => (Text.Text -> m (Maybe Value.Value))-  -> Name.Name-  -> CacheT m (Maybe Value.Value)-cached f x = do-  let key = LazyText.toStrict . Builder.toLazyText $ name x-  cache <- State.get-  case Map.lookup key cache of-    Just result -> pure result-    Nothing -> do-      result <- Trans.lift $ f key-      State.modify $ Map.insert key result-      pure result--template-  :: Monad m-  => (Name.Name -> CacheT m (Maybe Value.Value))-  -> Template.Template-  -> CacheT m Builder.Builder-template f = fmap mconcat . traverse (token f) . Template.tokens--token-  :: Monad m-  => (Name.Name -> CacheT m (Maybe Value.Value))-  -> Token.Token-  -> CacheT m Builder.Builder-token f x = case x of-  Token.Expression y -> expression f y-  Token.Literal y -> pure $ literal y--expression-  :: Monad m-  => (Name.Name -> CacheT m (Maybe Value.Value))-  -> Expression.Expression-  -> CacheT m Builder.Builder-expression f ex =-  let op = Expression.operator ex-  in-    fmap-      (mconcat-      . (\xs -> if null xs then xs else prefix op : xs)-      . List.intersperse (separator op)-      . Maybe.catMaybes-      )-    . traverse (variable f op)-    . NonEmpty.toList-    $ Expression.variables ex--separator :: Operator.Operator -> Builder.Builder-separator op = Builder.singleton $ case op of-  Operator.Ampersand -> '&'-  Operator.FullStop -> '.'-  Operator.None -> ','-  Operator.NumberSign -> ','-  Operator.PlusSign -> ','-  Operator.QuestionMark -> '&'-  Operator.Semicolon -> ';'-  Operator.Solidus -> '/'--prefix :: Operator.Operator -> Builder.Builder-prefix op = case op of-  Operator.Ampersand -> Builder.singleton '&'-  Operator.FullStop -> Builder.singleton '.'-  Operator.None -> mempty-  Operator.NumberSign -> Builder.singleton '#'-  Operator.PlusSign -> mempty-  Operator.QuestionMark -> Builder.singleton '?'-  Operator.Semicolon -> Builder.singleton ';'-  Operator.Solidus -> Builder.singleton '/'--variable-  :: Monad m-  => (Name.Name -> CacheT m (Maybe Value.Value))-  -> Operator.Operator-  -> Variable.Variable-  -> CacheT m (Maybe Builder.Builder)-variable f op var = do-  res <- f $ Variable.name var-  pure $ case res of-    Nothing -> Nothing-    Just val -> value op var val--value-  :: Operator.Operator-  -> Variable.Variable-  -> Value.Value-  -> Maybe Builder.Builder-value op var val = case val of-  Value.Dictionary xs -> dictionaryValue op var $ Map.toAscList xs-  Value.List xs -> listValue op var xs-  Value.String x -> Just $ stringValue op var x--dictionaryValue-  :: Operator.Operator-  -> Variable.Variable-  -> [(Text.Text, Text.Text)]-  -> Maybe Builder.Builder-dictionaryValue = items $ \op var (k, v) ->-  let f = string op Modifier.None-  in-    case Variable.modifier var of-      Modifier.Asterisk -> [f k <> Builder.singleton '=' <> f v]-      _ -> [f k, f v]--listValue-  :: Operator.Operator-  -> Variable.Variable-  -> [Text.Text]-  -> Maybe Builder.Builder-listValue = items $ \op var -> pure . stringValue-  (case Variable.modifier var of-    Modifier.Asterisk -> op-    _ -> Operator.None-  )-  var { Variable.modifier = Modifier.None }--items-  :: (Operator.Operator -> Variable.Variable -> a -> [Builder.Builder])-  -> Operator.Operator-  -> Variable.Variable-  -> [a]-  -> Maybe Builder.Builder-items f op var xs =-  let-    md = Variable.modifier var-    sep = case md of-      Modifier.Asterisk -> separator op-      _ -> Builder.singleton ','-    p = case md of-      Modifier.Asterisk -> False-      _ -> case op of-        Operator.Ampersand -> True-        Operator.QuestionMark -> True-        Operator.Semicolon -> True-        _ -> False-  in if null xs-    then Nothing-    else-      Just-      . mconcat-      . (if p then (label True var :) else id)-      . List.intersperse sep-      $ concatMap (f op var) xs--label :: Bool -> Variable.Variable -> Builder.Builder-label p v =-  name (Variable.name v) <> if p then Builder.singleton '=' else mempty--name :: Name.Name -> Builder.Builder-name =-  mconcat-    . List.intersperse (Builder.singleton '.')-    . fmap field-    . NonEmpty.toList-    . Name.fields--field :: Field.Field -> Builder.Builder-field = foldMap (character $ const True) . Field.characters--character :: (Char -> Bool) -> Character.Character tag -> Builder.Builder-character f x = case x of-  Character.Encoded y z -> Render.encodedCharacter y z-  Character.Unencoded y -> unencodedCharacter f y--stringValue-  :: Operator.Operator -> Variable.Variable -> Text.Text -> Builder.Builder-stringValue op var str =-  let-    pre = case op of-      Operator.Ampersand -> label True var-      Operator.QuestionMark -> label True var-      Operator.Semicolon -> label (not $ Text.null str) var-      _ -> mempty-  in pre <> string op (Variable.modifier var) str--string-  :: Operator.Operator -> Modifier.Modifier -> Text.Text -> Builder.Builder-string op md =-  let-    allowed x = case op of-      Operator.NumberSign -> isAllowed x-      Operator.PlusSign -> isAllowed x-      _ -> isUnreserved x-    trim = case md of-      Modifier.Colon ml -> Text.take $ MaxLength.count ml-      _ -> id-  in foldMap (unencodedCharacter allowed) . Text.unpack . trim--isAllowed :: Char -> Bool-isAllowed x = isUnreserved x || isReserved x--isUnreserved :: Char -> Bool-isUnreserved x = case x of-  '-' -> True-  '.' -> True-  '_' -> True-  '~' -> True-  _ -> Char.isAsciiUpper x || Char.isAsciiLower x || Char.isDigit x--isReserved :: Char -> Bool-isReserved x = case x of-  '!' -> True-  '$' -> True-  '&' -> True-  '\'' -> True-  '(' -> True-  ')' -> True-  '*' -> True-  '+' -> True-  ',' -> True-  ';' -> True-  '=' -> True-  ':' -> True-  '/' -> True-  '?' -> True-  '#' -> True-  '[' -> True-  ']' -> True-  '@' -> True-  _ -> False--unencodedCharacter :: (Char -> Bool) -> Char -> Builder.Builder-unencodedCharacter f x = if f x-  then Builder.singleton x-  else foldMap (uncurry Render.encodedCharacter) $ encodeCharacter x--encodeCharacter :: Char -> [(Digit.Digit, Digit.Digit)]-encodeCharacter =-  fmap Digit.fromWord8 . ByteString.unpack . Text.encodeUtf8 . Text.singleton--literal :: Literal.Literal -> Builder.Builder-literal = foldMap (character isAllowed) . Literal.characters
− src/lib/Burrito/Internal/Match.hs
@@ -1,273 +0,0 @@-{-# OPTIONS_GHC -Wno-missing-export-lists #-}--module Burrito.Internal.Match where--import qualified Burrito.Internal.Expand as Expand-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.Literal as Literal-import qualified Burrito.Internal.Type.Match as Match-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.ByteString as ByteString-import qualified Data.Char as Char-import qualified Data.List as List-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import qualified Text.ParserCombinators.ReadP as ReadP---- | Matches a string against a template. This is essentially the opposite of--- @expand@.------ Since there isn't always one unique match, this function returns all the--- possibilities. It's up to you to select the one that makes the most sense,--- or to simply grab the first one if you don't care.------ >>> match "" <$> parse "no-match"--- Just []--- >>> match "no-variables" <$> parse "no-variables"--- Just [[]]--- >>> match "1-match" <$> parse "{one}-match"--- Just [[("one",String "1")]]------ Be warned that the number of possible matches can grow quickly if your--- template has variables next to each other without any separators.------ >>> let Just template = parse "{a}{b}"--- >>> mapM_ print $ match "ab" template--- [("a",String "a"),("b",String "b")]--- [("a",String "ab"),("b",String "")]--- [("a",String "ab")]--- [("a",String ""),("b",String "ab")]--- [("b",String "ab")]------ Matching supports everything /except/ explode modifiers (@{a*}@), list--- values, and dictionary values.-match :: String -> Template.Template -> [[(String, Value.Value)]]-match s =-  fmap finalize-    . Maybe.mapMaybe (keepConsistent . fst)-    . flip ReadP.readP_to_S s-    . template--finalize :: [(Name.Name, Match.Match)] -> [(String, Value.Value)]-finalize = Maybe.mapMaybe $ \(n, m) -> case m of-  Match.Defined v ->-    Just (Render.builderToString $ Render.name n, Value.String v)-  Match.Prefix _ v ->-    Just (Render.builderToString $ Render.name n, Value.String v)-  Match.Undefined -> Nothing--keepConsistent-  :: [(Name.Name, Match.Match)] -> Maybe [(Name.Name, Match.Match)]-keepConsistent xs = case xs of-  [] -> Just xs-  (k, v) : ys -> do-    let (ts, fs) = List.partition ((== k) . fst) ys-    w <- combine v $ fmap snd ts-    ((k, w) :) <$> keepConsistent fs--combine :: Match.Match -> [Match.Match] -> Maybe Match.Match-combine x ys = case ys of-  [] -> Just x-  y : zs -> case x of-    Match.Defined t -> case y of-      Match.Defined u | t == u -> combine x zs-      Match.Prefix m u | Text.take (MaxLength.count m) t == u -> combine x zs-      _ -> Nothing-    Match.Prefix n t -> case y of-      Match.Defined u | t == Text.take (MaxLength.count n) u -> combine y zs-      Match.Prefix m u-        | let c = MaxLength.count (min n m) in Text.take c t == Text.take c u-        -> combine (if m > n then y else x) zs-      _ -> Nothing-    Match.Undefined -> case y of-      Match.Undefined -> combine x zs-      _ -> Nothing--template :: Template.Template -> ReadP.ReadP [(Name.Name, Match.Match)]-template x = do-  xs <- fmap mconcat . traverse token $ Template.tokens x-  ReadP.eof-  pure xs--token :: Token.Token -> ReadP.ReadP [(Name.Name, Match.Match)]-token x = case x of-  Token.Expression y -> expression y-  Token.Literal y -> [] <$ literal y--expression :: Expression.Expression -> ReadP.ReadP [(Name.Name, Match.Match)]-expression x = variables (Expression.operator x) (Expression.variables x)--variables-  :: Operator.Operator-  -> NonEmpty.NonEmpty Variable.Variable-  -> ReadP.ReadP [(Name.Name, Match.Match)]-variables op vs = case op of-  Operator.Ampersand -> vars vs (Just '&') '&' varEq-  Operator.FullStop -> vars vs (Just '.') '.' $ variable Expand.isUnreserved-  Operator.None -> vars vs Nothing ',' $ variable Expand.isUnreserved-  Operator.NumberSign -> vars vs (Just '#') ',' $ variable Expand.isAllowed-  Operator.PlusSign -> vars vs Nothing ',' $ variable Expand.isAllowed-  Operator.QuestionMark -> vars vs (Just '?') '&' varEq-  Operator.Semicolon -> vars vs (Just ';') ';' $ \v -> do-    let n = Variable.name v-    name n-    ReadP.option [(n, Match.Defined Text.empty)] $ do-      char_ '='-      variable Expand.isUnreserved v-  Operator.Solidus -> vars vs (Just '/') '/' $ variable Expand.isUnreserved--vars-  :: NonEmpty.NonEmpty Variable.Variable-  -> Maybe Char-  -> Char-  -> (Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)])-  -> ReadP.ReadP [(Name.Name, Match.Match)]-vars vs m c f = do-  let-    ctx = case m of-      Nothing -> id-      Just o -> \p -> ReadP.option (undef <$> NonEmpty.toList vs) $ do-        char_ o-        xs <- p-        Monad.guard . not $ all isUndefined xs-        pure xs-  ctx . vars' c f $ NonEmpty.toList vs--isUndefined :: (Name.Name, Match.Match) -> Bool-isUndefined = (== Match.Undefined) . snd--vars'-  :: Char-  -> (Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)])-  -> [Variable.Variable]-  -> ReadP.ReadP [(Name.Name, Match.Match)]-vars' c f vs = case vs of-  [] -> pure []-  v : ws ->-    let-      this = do-        x <- f v-        xs <- ReadP.option (undef <$> ws) $ do-          char_ c-          vars' c f ws-        pure $ x <> xs-      that = (undef v :) <$> vars' c f ws-    in this ReadP.+++ that--undef :: Variable.Variable -> (Name.Name, Match.Match)-undef v = (Variable.name v, Match.Undefined)--char_ :: Char -> ReadP.ReadP ()-char_ = Monad.void . ReadP.char--varEq :: Variable.Variable -> ReadP.ReadP [(Name.Name, Match.Match)]-varEq v = do-  name $ Variable.name v-  char_ '='-  variable Expand.isUnreserved v--name :: Name.Name -> ReadP.ReadP ()-name = Monad.void . ReadP.string . Render.builderToString . Render.name--variable-  :: (Char -> Bool)-  -> Variable.Variable-  -> ReadP.ReadP [(Name.Name, Match.Match)]-variable f x = do-  v <- case Variable.modifier x of-    Modifier.Asterisk -> ReadP.pfail-    Modifier.None -> Match.Defined <$> manyCharacters f-    Modifier.Colon n -> Match.Prefix n <$> manyCharacters f-  pure [(Variable.name x, v)]--manyCharacters :: (Char -> Bool) -> ReadP.ReadP Text.Text-manyCharacters f = do-  let-    f1 = (:) <$> someEncodedCharacters <*> ReadP.option [] f2-    f2 = (:) <$> someUnencodedCharacters f <*> ReadP.option [] f1-  fmap mconcat . ReadP.option [] $ f1 ReadP.<++ f2--someEncodedCharacters :: ReadP.ReadP Text.Text-someEncodedCharacters = do-  xs <- some anEncodedCharacter-  either (fail . show) pure-    . Text.decodeUtf8'-    . ByteString.pack-    . fmap (uncurry Digit.toWord8)-    $ NonEmpty.toList xs--some :: ReadP.ReadP a -> ReadP.ReadP (NonEmpty.NonEmpty a)-some p = (NonEmpty.:|) <$> p <*> ReadP.many p--someUnencodedCharacters :: (Char -> Bool) -> ReadP.ReadP Text.Text-someUnencodedCharacters f = do-  xs <- some $ ReadP.satisfy f-  pure . Text.pack $ NonEmpty.toList xs--anEncodedCharacter :: ReadP.ReadP (Digit.Digit, Digit.Digit)-anEncodedCharacter = do-  char_ '%'-  (,) <$> aDigit <*> aDigit--aDigit :: ReadP.ReadP Digit.Digit-aDigit = do-  x <- ReadP.satisfy Char.isHexDigit-  maybe (fail "invalid Digit") pure $ Digit.fromChar x--literal :: Literal.Literal -> ReadP.ReadP ()-literal = mapM_ literalCharacter . Literal.characters--literalCharacter :: Character.Character Literal.Literal -> ReadP.ReadP ()-literalCharacter = character Expand.isAllowed--character :: (Char -> Bool) -> Character.Character tag -> ReadP.ReadP ()-character f x = case x of-  Character.Encoded y z -> encodedCharacter y z-  Character.Unencoded y -> unencodedCharacter f y--encodedCharacter :: Digit.Digit -> Digit.Digit -> ReadP.ReadP ()-encodedCharacter x y = char_ '%' *> digit x *> digit y--digit :: Digit.Digit -> ReadP.ReadP ()-digit x = char_ $ case x of-  Digit.Ox0 -> '0'-  Digit.Ox1 -> '1'-  Digit.Ox2 -> '2'-  Digit.Ox3 -> '3'-  Digit.Ox4 -> '4'-  Digit.Ox5 -> '5'-  Digit.Ox6 -> '6'-  Digit.Ox7 -> '7'-  Digit.Ox8 -> '8'-  Digit.Ox9 -> '9'-  Digit.OxA Case.Upper -> 'A'-  Digit.OxB Case.Upper -> 'B'-  Digit.OxC Case.Upper -> 'C'-  Digit.OxD Case.Upper -> 'D'-  Digit.OxE Case.Upper -> 'E'-  Digit.OxF Case.Upper -> 'F'-  Digit.OxA Case.Lower -> 'a'-  Digit.OxB Case.Lower -> 'b'-  Digit.OxC Case.Lower -> 'c'-  Digit.OxD Case.Lower -> 'd'-  Digit.OxE Case.Lower -> 'e'-  Digit.OxF Case.Lower -> 'f'--unencodedCharacter :: (Char -> Bool) -> Char -> ReadP.ReadP ()-unencodedCharacter f x = if f x-  then char_ x-  else mapM_ (uncurry encodedCharacter) $ Expand.encodeCharacter x
− src/lib/Burrito/Internal/Parse.hs
@@ -1,175 +0,0 @@-{-# OPTIONS_GHC -Wno-missing-export-lists #-}--{-# LANGUAGE FlexibleContexts #-}--module Burrito.Internal.Parse where--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.Variable as Variable-import qualified Data.Char as Char-import qualified Data.Ix as Ix-import qualified Data.List.NonEmpty as NonEmpty-import qualified Text.Parsec as Parsec-import qualified Text.Read as Read---- | Attempts to parse a string as a URI template. If parsing fails, this will--- return @Nothing@. Otherwise it will return @Just@ the parsed template.------ Parsing will usually succeed, but it can fail if the input string contains--- characters that are not valid in IRIs (like @^@) or if the input string--- contains an invalid template expression (like @{!}@). To include characters--- that aren't valid in IRIs, percent encode them (like @%5E@).------ >>> parse "invalid template"--- Nothing--- >>> parse "valid-template"--- Just (Template ...)-parse :: String -> Maybe Template.Template-parse = either (const Nothing) Just . Parsec.parse template ""--template :: Parsec.Stream s m Char => Parsec.ParsecT s u m Template.Template-template = Template.Template <$> Parsec.many token <* Parsec.eof--token :: Parsec.Stream s m Char => Parsec.ParsecT s u m Token.Token-token = choice (Token.Expression <$> expression) (Token.Literal <$> literal)--choice-  :: Parsec.ParsecT s u m a -> Parsec.ParsecT s u m a -> Parsec.ParsecT s u m a-choice = (Parsec.<|>)--expression-  :: Parsec.Stream s m Char => Parsec.ParsecT s u m Expression.Expression-expression =-  Parsec.between (Parsec.char '{') (Parsec.char '}')-    $ Expression.Expression-    <$> operator-    <*> sepBy1 variable (Parsec.char ',')--operator :: Parsec.Stream s m Char => Parsec.ParsecT s u m Operator.Operator-operator = Parsec.option Operator.None $ Parsec.choice-  [ Operator.Ampersand <$ Parsec.char '&'-  , Operator.FullStop <$ Parsec.char '.'-  , Operator.NumberSign <$ Parsec.char '#'-  , Operator.PlusSign <$ Parsec.char '+'-  , Operator.QuestionMark <$ Parsec.char '?'-  , Operator.Semicolon <$ Parsec.char ';'-  , Operator.Solidus <$ Parsec.char '/'-  ]--sepBy1-  :: Parsec.ParsecT s u m a-  -> Parsec.ParsecT s u m x-  -> Parsec.ParsecT s u m (NonEmpty.NonEmpty a)-sepBy1 p s = (NonEmpty.:|) <$> p <*> Parsec.many (s *> p)--variable :: Parsec.Stream s m Char => Parsec.ParsecT s u m Variable.Variable-variable = Variable.Variable <$> name <*> modifier--name :: Parsec.Stream s m Char => Parsec.ParsecT s u m Name.Name-name = Name.Name <$> sepBy1 field (Parsec.char '.')--field :: Parsec.Stream s m Char => Parsec.ParsecT s u m Field.Field-field = Field.Field <$> nonEmpty fieldCharacter--nonEmpty-  :: Parsec.ParsecT s u m a -> Parsec.ParsecT s u m (NonEmpty.NonEmpty a)-nonEmpty p = (NonEmpty.:|) <$> p <*> Parsec.many p--fieldCharacter-  :: Parsec.Stream s m Char-  => Parsec.ParsecT s u m (Character.Character Field.Field)-fieldCharacter = choice encodedCharacter (unencodedCharacter isFieldCharacter)--encodedCharacter-  :: Parsec.Stream s m Char => Parsec.ParsecT s u m (Character.Character tag)-encodedCharacter = Parsec.char '%' >> Character.Encoded <$> digit <*> digit--digit :: Parsec.Stream s m Char => Parsec.ParsecT s u m Digit.Digit-digit = do-  x <- Parsec.satisfy Char.isHexDigit-  maybe (fail "invalid Digit") pure $ Digit.fromChar x--unencodedCharacter-  :: Parsec.Stream s m Char-  => (Char -> Bool)-  -> Parsec.ParsecT s u m (Character.Character tag)-unencodedCharacter = fmap Character.Unencoded . Parsec.satisfy--isFieldCharacter :: Char -> Bool-isFieldCharacter x = case x of-  '_' -> True-  _ -> Char.isAsciiUpper x || Char.isAsciiLower x || Char.isDigit x--modifier :: Parsec.Stream s m Char => Parsec.ParsecT s u m Modifier.Modifier-modifier = Parsec.option Modifier.None $ Parsec.choice-  [ Modifier.Asterisk <$ Parsec.char '*'-  , Parsec.char ':' >> Modifier.Colon <$> maxLength-  ]--maxLength :: Parsec.Stream s m Char => Parsec.ParsecT s u m MaxLength.MaxLength-maxLength = do-  x <- Parsec.satisfy $ Ix.inRange ('1', '9')-  xs <- Parsec.many $ Parsec.satisfy Char.isDigit-  n <- maybe (fail "invalid MaxLength") pure . Read.readMaybe $ x : xs-  if isMaxLength n-    then pure $ MaxLength.MaxLength n-    else fail "invalid MaxLength"--isMaxLength :: Int -> Bool-isMaxLength = Ix.inRange (1, 9999)--literal :: Parsec.Stream s m Char => Parsec.ParsecT s u m Literal.Literal-literal = Literal.Literal <$> nonEmpty literalCharacter--literalCharacter-  :: Parsec.Stream s m Char-  => Parsec.ParsecT s u m (Character.Character Literal.Literal)-literalCharacter =-  choice encodedCharacter (unencodedCharacter isLiteralCharacter)--isLiteralCharacter :: Char -> Bool-isLiteralCharacter x = case x of-  ' ' -> False-  '"' -> False-  '\'' -> False-  '%' -> False-  '<' -> False-  '>' -> False-  '\\' -> False-  '^' -> False-  '`' -> False-  '{' -> False-  '|' -> False-  '}' -> False-  _ ->-    Ix.inRange ('\x20', '\x7e') x-      || Ix.inRange ('\xa0', '\xd7ff') x-      || Ix.inRange ('\xe000', '\xf8ff') x-      || Ix.inRange ('\xf900', '\xfdcf') x-      || Ix.inRange ('\xfdf0', '\xffef') x-      || Ix.inRange ('\x10000', '\x1fffd') x-      || Ix.inRange ('\x20000', '\x2fffd') x-      || Ix.inRange ('\x30000', '\x3fffd') x-      || Ix.inRange ('\x40000', '\x4fffd') x-      || Ix.inRange ('\x50000', '\x5fffd') x-      || Ix.inRange ('\x60000', '\x6fffd') x-      || Ix.inRange ('\x70000', '\x7fffd') x-      || Ix.inRange ('\x80000', '\x8fffd') x-      || Ix.inRange ('\x90000', '\x9fffd') x-      || Ix.inRange ('\xa0000', '\xafffd') x-      || Ix.inRange ('\xb0000', '\xbfffd') x-      || Ix.inRange ('\xc0000', '\xcfffd') x-      || Ix.inRange ('\xd0000', '\xdfffd') x-      || Ix.inRange ('\xe1000', '\xefffd') x-      || Ix.inRange ('\xf0000', '\xffffd') x-      || Ix.inRange ('\x100000', '\x10fffd') x
− src/lib/Burrito/Internal/Render.hs
@@ -1,107 +0,0 @@-{-# OPTIONS_GHC -Wno-missing-export-lists #-}--module Burrito.Internal.Render where--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.Token as Token-import qualified Burrito.Internal.Type.Variable as Variable-import qualified Data.List as List-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Text.Lazy as LazyText-import qualified Data.Text.Lazy.Builder as Builder--builderToString :: Builder.Builder -> String-builderToString = LazyText.unpack . Builder.toLazyText--token :: Token.Token -> Builder.Builder-token x = case x of-  Token.Expression y -> expression y-  Token.Literal y -> literal y--expression :: Expression.Expression -> Builder.Builder-expression x =-  Builder.singleton '{'-    <> operator (Expression.operator x)-    <> sepBy1 variable (Builder.singleton ',') (Expression.variables x)-    <> Builder.singleton '}'--operator :: Operator.Operator -> Builder.Builder-operator x = case x of-  Operator.Ampersand -> Builder.singleton '&'-  Operator.FullStop -> Builder.singleton '.'-  Operator.None -> mempty-  Operator.NumberSign -> Builder.singleton '#'-  Operator.PlusSign -> Builder.singleton '+'-  Operator.QuestionMark -> Builder.singleton '?'-  Operator.Semicolon -> Builder.singleton ';'-  Operator.Solidus -> Builder.singleton '/'--sepBy1-  :: (a -> Builder.Builder)-  -> Builder.Builder-  -> NonEmpty.NonEmpty a-  -> Builder.Builder-sepBy1 f x = mconcat . List.intersperse x . fmap f . NonEmpty.toList--variable :: Variable.Variable -> Builder.Builder-variable x = name (Variable.name x) <> modifier (Variable.modifier x)--name :: Name.Name -> Builder.Builder-name = sepBy1 field (Builder.singleton '.') . Name.fields--field :: Field.Field -> Builder.Builder-field = foldMap character . Field.characters--character :: Character.Character tag -> Builder.Builder-character x = case x of-  Character.Encoded y z -> encodedCharacter y z-  Character.Unencoded y -> Builder.singleton y--encodedCharacter :: Digit.Digit -> Digit.Digit -> Builder.Builder-encodedCharacter x y = Builder.singleton '%' <> digit x <> digit y--digit :: Digit.Digit -> Builder.Builder-digit x = Builder.singleton $ case x of-  Digit.Ox0 -> '0'-  Digit.Ox1 -> '1'-  Digit.Ox2 -> '2'-  Digit.Ox3 -> '3'-  Digit.Ox4 -> '4'-  Digit.Ox5 -> '5'-  Digit.Ox6 -> '6'-  Digit.Ox7 -> '7'-  Digit.Ox8 -> '8'-  Digit.Ox9 -> '9'-  Digit.OxA Case.Upper -> 'A'-  Digit.OxB Case.Upper -> 'B'-  Digit.OxC Case.Upper -> 'C'-  Digit.OxD Case.Upper -> 'D'-  Digit.OxE Case.Upper -> 'E'-  Digit.OxF Case.Upper -> 'F'-  Digit.OxA Case.Lower -> 'a'-  Digit.OxB Case.Lower -> 'b'-  Digit.OxC Case.Lower -> 'c'-  Digit.OxD Case.Lower -> 'd'-  Digit.OxE Case.Lower -> 'e'-  Digit.OxF Case.Lower -> 'f'--modifier :: Modifier.Modifier -> Builder.Builder-modifier x = case x of-  Modifier.Asterisk -> Builder.singleton '*'-  Modifier.Colon y -> Builder.singleton ':' <> maxLength y-  Modifier.None -> mempty--maxLength :: MaxLength.MaxLength -> Builder.Builder-maxLength = Builder.fromString . show . MaxLength.count--literal :: Literal.Literal -> Builder.Builder-literal = foldMap character . Literal.characters
− src/lib/Burrito/Internal/TH.hs
@@ -1,28 +0,0 @@-module Burrito.Internal.TH (uriTemplate) where--import qualified Burrito.Internal.Parse as Parse-import qualified Language.Haskell.TH.Quote as TH-import qualified Language.Haskell.TH.Syntax as TH---- | This can be used together with the @QuasiQuotes@ language extension to--- parse a URI template at compile time. This is convenient because it allows--- you to verify the validity of the template when you compile your file as--- opposed to when you run it.------ >>> :set -XQuasiQuotes--- >>> import Burrito--- >>> let template = [uriTemplate|http://example/search{?query}|]--- >>> let values = [("query", stringValue "chorizo")]--- >>> expand values template--- "http://example/search?query=chorizo"------ Note that you cannot use escape sequences in this quasi-quoter. For example,--- this is invalid: @[uriTemplate|\\xa0|]@. You can however use percent encoded--- triples as normal. So this is valid: @[uriTemplate|%c2%a0|]@.-uriTemplate :: TH.QuasiQuoter-uriTemplate = TH.QuasiQuoter-  { TH.quoteDec = const $ fail "cannot be used as a declaration"-  , TH.quoteExp = maybe (fail "invalid URI template") TH.liftData . Parse.parse-  , TH.quotePat = const $ fail "cannot be used as a pattern"-  , TH.quoteType = const $ fail "cannot be used as a type"-  }
− src/lib/Burrito/Internal/Type/Case.hs
@@ -1,10 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Case (Case(..)) where--import qualified Data.Data as Data--data Case-  = Lower-  | Upper-  deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Character.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Character (Character(..)) where--import qualified Burrito.Internal.Type.Digit as Digit-import qualified Data.Data as Data--data Character tag-  = Encoded Digit.Digit Digit.Digit-  | Unencoded Char-  deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Digit.hs
@@ -1,100 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Digit (Digit(..), fromChar, fromWord8, toWord8) where--import qualified Burrito.Internal.Type.Case as Case-import qualified Data.Bits as Bits-import qualified Data.Word as Word-import qualified Data.Data as Data--data Digit-  = Ox0-  | Ox1-  | Ox2-  | Ox3-  | Ox4-  | Ox5-  | Ox6-  | Ox7-  | Ox8-  | Ox9-  | OxA Case.Case-  | OxB Case.Case-  | OxC Case.Case-  | OxD Case.Case-  | OxE Case.Case-  | OxF Case.Case-  deriving (Data.Data, Eq, Ord, Show)--fromChar :: Char -> Maybe Digit-fromChar x = case x of-  '0' -> Just Ox0-  '1' -> Just Ox1-  '2' -> Just Ox2-  '3' -> Just Ox3-  '4' -> Just Ox4-  '5' -> Just Ox5-  '6' -> Just Ox6-  '7' -> Just Ox7-  '8' -> Just Ox8-  '9' -> Just Ox9-  'A' -> Just $ OxA Case.Upper-  'B' -> Just $ OxB Case.Upper-  'C' -> Just $ OxC Case.Upper-  'D' -> Just $ OxD Case.Upper-  'E' -> Just $ OxE Case.Upper-  'F' -> Just $ OxF Case.Upper-  'a' -> Just $ OxA Case.Lower-  'b' -> Just $ OxB Case.Lower-  'c' -> Just $ OxC Case.Lower-  'd' -> Just $ OxD Case.Lower-  'e' -> Just $ OxE Case.Lower-  'f' -> Just $ OxF Case.Lower-  _ -> Nothing--fromWord8 :: Word.Word8 -> (Digit, Digit)-fromWord8 x =-  let-    f :: Word.Word8 -> Digit-    f y = case y of-      0x0 -> Ox0-      0x1 -> Ox1-      0x2 -> Ox2-      0x3 -> Ox3-      0x4 -> Ox4-      0x5 -> Ox5-      0x6 -> Ox6-      0x7 -> Ox7-      0x8 -> Ox8-      0x9 -> Ox9-      0xA -> OxA Case.Upper-      0xB -> OxB Case.Upper-      0xC -> OxC Case.Upper-      0xD -> OxD Case.Upper-      0xE -> OxE Case.Upper-      0xF -> OxF Case.Upper-      _ -> error $ "invalid nibble: " <> show y-  in (f $ Bits.shiftR x 4, f $ x Bits..&. 0x0F)--toWord8 :: Digit -> Digit -> Word.Word8-toWord8 x y =-  let-    f :: Digit -> Word.Word8-    f z = case z of-      Ox0 -> 0x0-      Ox1 -> 0x1-      Ox2 -> 0x2-      Ox3 -> 0x3-      Ox4 -> 0x4-      Ox5 -> 0x5-      Ox6 -> 0x6-      Ox7 -> 0x7-      Ox8 -> 0x8-      Ox9 -> 0x9-      OxA _ -> 0xA-      OxB _ -> 0xB-      OxC _ -> 0xC-      OxD _ -> 0xD-      OxE _ -> 0xE-      OxF _ -> 0xF-  in Bits.shiftL (f x) 4 Bits..|. f y
− src/lib/Burrito/Internal/Type/Expression.hs
@@ -1,13 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Expression (Expression(..)) where--import qualified Burrito.Internal.Type.Operator as Operator-import qualified Burrito.Internal.Type.Variable as Variable-import qualified Data.Data as Data-import qualified Data.List.NonEmpty as NonEmpty--data Expression = Expression-  { operator :: Operator.Operator-  , variables :: NonEmpty.NonEmpty Variable.Variable-  } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Field.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Field (Field(..)) where--import qualified Burrito.Internal.Type.Character as Character-import qualified Data.Data as Data-import qualified Data.List.NonEmpty as NonEmpty--newtype Field = Field-  { characters :: NonEmpty.NonEmpty (Character.Character Field)-  } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Literal.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Literal (Literal(..)) where--import qualified Burrito.Internal.Type.Character as Character-import qualified Data.Data as Data-import qualified Data.List.NonEmpty as NonEmpty--newtype Literal = Literal-  { characters :: NonEmpty.NonEmpty (Character.Character Literal)-  } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Match.hs
@@ -1,13 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Match (Match(..)) where--import qualified Burrito.Internal.Type.MaxLength as MaxLength-import qualified Data.Data as Data-import qualified Data.Text as Text--data Match-  = Defined Text.Text-  | Prefix MaxLength.MaxLength Text.Text-  | Undefined-  deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/MaxLength.hs
@@ -1,9 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.MaxLength (MaxLength(..)) where--import qualified Data.Data as Data--newtype MaxLength = MaxLength-  { count :: Int-  } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Modifier.hs
@@ -1,12 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Modifier (Modifier(..)) where--import qualified Burrito.Internal.Type.MaxLength as MaxLength-import qualified Data.Data as Data--data Modifier-  = Asterisk-  | Colon MaxLength.MaxLength-  | None-  deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Name.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Name (Name(..)) where--import qualified Burrito.Internal.Type.Field as Field-import qualified Data.Data as Data-import qualified Data.List.NonEmpty as NonEmpty--newtype Name = Name-  { fields :: NonEmpty.NonEmpty Field.Field-  } deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Operator.hs
@@ -1,16 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Operator (Operator(..)) where--import qualified Data.Data as Data--data Operator-  = Ampersand-  | FullStop-  | None-  | NumberSign-  | PlusSign-  | QuestionMark-  | Semicolon-  | Solidus-  deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Template.hs
@@ -1,31 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Template (Template (..), render) where--import qualified Burrito.Internal.Render as Render-import qualified Burrito.Internal.Type.Token as Token-import qualified Data.Data as Data-import qualified Data.Text.Lazy.Builder as Builder---- | Represents a URI template.-newtype Template = Template-  { tokens :: [Token.Token]-  } deriving (Data.Data, Eq, Ord)--instance Show Template where-  show = render---- | Renders a template back into a string. This is essentially the opposite of--- @parse@. Usually you'll want to use @expand@ to actually substitute--- variables in the template, but this can be useful for printing out the--- template itself------ >>> render <$> parse "valid-template"--- Just "valid-template"--- >>> render <$> parse "{var}"--- Just "{var}"-render :: Template -> String-render = Render.builderToString . template--template :: Template -> Builder.Builder-template = foldMap Render.token . tokens
− src/lib/Burrito/Internal/Type/Token.hs
@@ -1,12 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Token (Token(..)) where--import qualified Burrito.Internal.Type.Expression as Expression-import qualified Burrito.Internal.Type.Literal as Literal-import qualified Data.Data as Data--data Token-  = Expression Expression.Expression-  | Literal Literal.Literal-  deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Value.hs
@@ -1,16 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Value (Value(..)) where--import qualified Data.Data as Data-import qualified Data.Map as Map-import qualified Data.Text as Text---- | Represents a value that can be substituted into a template. Can be a--- string, a list, or dictionary (which is called an associative array in the--- RFC).-data Value-  = Dictionary (Map.Map Text.Text Text.Text)-  | List [Text.Text]-  | String Text.Text-  deriving (Data.Data, Eq, Ord, Show)
− src/lib/Burrito/Internal/Type/Variable.hs
@@ -1,12 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Burrito.Internal.Type.Variable (Variable(..)) where--import qualified Burrito.Internal.Type.Modifier as Modifier-import qualified Burrito.Internal.Type.Name as Name-import qualified Data.Data as Data--data Variable = Variable-  { name :: Name.Name-  , modifier :: Modifier.Modifier-  } deriving (Data.Data, Eq, Ord, Show)
− src/test/Main.hs
@@ -1,1471 +0,0 @@-{-# OPTIONS_GHC -Wno-monomorphism-restriction #-}--{-# 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 (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