@@ -1,6 +1,6 @@
1{-# LANGUAGE GeneralizedNewtypeDeriving #-}
1{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3−module Language.Nix.Identifier ( Identifier, string, quotedString, needsQuoting ) where
3+module Language.Nix.Identifier ( Identifier(..), ident, quote, needsQuoting ) where
5import Data.Char
5import Data.Char
6import Data.Function
6import Data.Function
@@ -11,41 +11,37 @@ import Text.Regex.Posix
12-- | Identifiers in Nix are essentially strings. Reasonable people restrict
12-- | Identifiers in Nix are essentially strings. Reasonable people restrict
13-- themselves to identifiers of the form @[a-zA-Z\_][a-zA-Z0-9\_\'\-]*@,
13-- themselves to identifiers of the form @[a-zA-Z\_][a-zA-Z0-9\_\'\-]*@,
14−-- though, because these don't need quoting. The @Identifier@ type is an
14+-- because these don't need quoting. The @Identifier@ type is an instance of
15−-- instance of the 'IsString' class for construction. The methods of the
15+-- the 'IsString' class for convenience. The methods of the 'Pretty' class can
16−-- 'Pretty' class can be used to pretty-print an identifier with proper
16+-- be used to pretty-print an identifier with proper quoting.
19−-- >>> let i = fromString "test" :: Identifier in (i, pPrint i)
18+-- >>> let i = Identifier "test" in (i, pPrint i)
20-- (Identifier "test",test)
19-- (Identifier "test",test)
21−-- >>> let i = fromString "foo.bar" :: Identifier in (i, pPrint i)
20+-- >>> let i = Identifier "foo.bar" in (i, pPrint i)
22-- (Identifier "foo.bar","foo.bar")
21-- (Identifier "foo.bar","foo.bar")
24-- The 'Ord' instance for identifiers is unusual in that it's aware of
23-- The 'Ord' instance for identifiers is unusual in that it's aware of
25-- character's case:
24-- character's case:
27−-- >>> (fromString "abc" :: Identifier) == fromString "abc"
29−-- >>> (fromString "abc" :: Identifier) == fromString "ABC"
26+-- >>> Identifier "abc" == Identifier "ABC"
31−-- >>> (fromString "abc" :: Identifier) > fromString "ABC"
28+-- >>> Identifier "abc" > Identifier "ABC"
33−-- >>> (fromString "abc" :: Identifier) > fromString "ABC"
30+-- >>> Identifier "abc" > Identifier "ABC"
35−-- >>> (fromString "X" :: Identifier) > fromString "a"
32+-- >>> Identifier "X" > Identifier "a"
37−-- >>> (fromString "x" :: Identifier) > fromString "A"
34+-- >>> Identifier "x" > Identifier "A"
40-- prop> \str -> Identifier str == Identifier str
37-- prop> \str -> Identifier str == Identifier str
41-- prop> \str -> any (`elem` ['a'..'z']) str ==> Identifier (map toLower str) /= Identifier (map toUpper str)
38-- prop> \str -> any (`elem` ['a'..'z']) str ==> Identifier (map toLower str) /= Identifier (map toUpper str)
42−-- prop> \(NonEmpty str) -> head str `elem` ['a'..'z'] ==> Identifier (map toLower str) >= Identifier (map toUpper str)
44newtype Identifier = Identifier String
40newtype Identifier = Identifier String
45 deriving (Show, Eq, IsString)
41 deriving (Show, Eq, IsString)
47instance Pretty Identifier where
43instance Pretty Identifier where
48− pPrint i = text (i ^. string)
44+ pPrint i = text (i ^. ident . to quote)
50instance Ord Identifier where
46instance Ord Identifier where
51 compare (Identifier a) (Identifier b) =
47 compare (Identifier a) (Identifier b) =
@@ -53,33 +49,33 @@ instance Ord Identifier where
53 EQ -> compare a b
49 EQ -> compare a b
56−needsQuoting :: Identifier -> Bool
52+-- | Checks whether a given string would need quoting when interpreted as an
57−needsQuoting (Identifier str) = not (str =~ "^[a-zA-Z\\_][a-zA-Z0-9\\_\\'\\-]*$")
53+-- intentifier.
55+needsQuoting :: String -> Bool
56+needsQuoting str = not (str =~ grammar)
57+ where grammar :: String -- TODO: should be a compiled regular expression
58+ grammar = "^[a-zA-Z\\_][a-zA-Z0-9\\_\\'\\-]*$"
59−-- | Lens that allows conversion from/to the standard 'String' type.
60+-- | Lens that allows conversion from/to the standard 'String' type. The setter
60−--
61+-- does not evaluate its input, so it's safe to use with 'undefined'.
61−-- >>> putStrLn $ fromString "abc" ^. string
64−-- >>> putStrLn $ fromString "abc.def" ^. string
63+-- >>> putStrLn $ Identifier "abc.def" ^. ident
65−-- "abc.def"
64+-- abc.def
67−-- >>> pPrint $ fromString "" & string .~ "abcdef"
66+-- >>> pPrint $ undefined & ident .~ "abcdef"
70−string :: Lens' Identifier String
69+ident :: Lens' Identifier String
71−string f (Identifier str) = (\str' -> fromString str') `fmap` f s
70+ident f (Identifier str) = Identifier `fmap` f str
72− where s = if needsQuoting (Identifier str) then show str else str
74−-- | A variant of 'string' that gives read-only access to a string-representation
72+-- | Help function that quotes a given identifier string (if necessary).
75−-- of the identifier that is always quoted, even if the identifier actually
76−-- doesn't need quoting.
78−-- >>> putStrLn $ fromString "abc" ^. quotedString
74+-- >>> putStrLn (quote "abc")
81−-- >>> putStrLn $ fromString "abc.def" ^. quotedString
77+-- >>> putStrLn (quote "abc.def")
82-- "abc.def"
78-- "abc.def"
84−quotedString :: Getter Identifier String
80+quote :: String -> String
85−quotedString f (Identifier str) = fmap fromString (f (show str))
81+quote s = if needsQuoting s then show s else s