sext (empty) → 0.1.0.0
raw patch · 6 files changed
+484/−0 lines, 6 filesdep +basedep +bytestringdep +template-haskellsetup-changed
Dependencies added: base, bytestring, template-haskell, text
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- sext.cabal +57/−0
- src/Data/Sext.hs +219/−0
- src/Data/Sext/Class.hs +122/−0
- src/Data/Sext/TH.hs +54/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Dmitry Dzhus++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Dmitry Dzhus nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ sext.cabal view
@@ -0,0 +1,57 @@+name: sext+version: 0.1.0.0+synopsis: Lists, Texts and ByteStrings with type-encoded length+homepage: http://github.com/dzhus/sext/++description: Sext (/s/tatic t/ext/) provides type-level safety for+ basic operations on string-like types (finite+ lists of elements). Use it when you need static+ guarantee on lengths of strings produced in your+ code.++license: BSD3+license-file: LICENSE+author: Dmitry Dzhus+maintainer: dima@dzhus.org+category: Data, Text, Type System+stability: Experimental++build-type: Simple+cabal-version: >=1.10+tested-with: GHC == 7.8.3++source-repository head+ type: git+ location: http://github.com/dzhus/sext++flag text+ description: Build interface for Text+ default: True++flag bytestring+ description: Build interface for ByteString+ default: True++library+ exposed-modules:+ Data.Sext,+ Data.Sext.Class,+ Data.Sext.TH++ build-depends:+ base >=4.7 && <4.8,+ template-haskell++ if flag(bytestring)+ cpp-options: -DWITH_BS+ build-depends:+ bytestring >=0.10 && <0.11++ if flag(text)+ cpp-options: -DWITH_TEXT+ build-depends:+ text >=1.1 && <1.2++ ghc-options: -Wall+ hs-source-dirs: src+ default-language: Haskell2010
+ src/Data/Sext.hs view
@@ -0,0 +1,219 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}++{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++{-|++Sext (/s/tatic t/ext/) provides type-level safety for basic operations+on string-like types (finite lists of elements). Use it when you need+static guarantee on lengths of strings produced in your code.++An example application would be a network exchange protocol built of+packets with fixed-width fields:++> mkPacket :: String -> Sext 32 String+> mkPacket inp =+> -- 5-character version signature+> $(sext "PKT10") `append`+> -- 25-character payload+> payload `append`+> -- 2-character payload checksum+> checksum+> where+> payload = createLeft ' ' inp+> checksum :: Sext 2 String+> checksum = createLeft ' ' $+> show $ length payload `mod` 100+>+> message :: Sext 64 String+> message = mkPacket "Hello" `append` mkPacket "world"++Sext combinators are defined for members of 'Sextable' class. The+package includes 'Sextable' instances for several common types.++This module is meant to be imported qualifed, e.g.++> import qualified Data.Sext as S++-}++module Data.Sext+ (+ -- * Constructing Sexts+ --+ -- | See also 'C.unsafeCreate'+ createLeft+ , createRight+ , sext+ , create+ , replicate++ -- * Working with Sexts+ , append+ , take+ , drop+ , map+ , padLeft+ , padRight++ , length++ -- * Sextable class+ , Sextable(C.unsafeCreate, C.unwrap)+ )++where++import Prelude as P hiding (drop, length, map, replicate, take)+import qualified Prelude as P hiding (length)++import GHC.TypeLits++import Data.Proxy+import Data.Sext.Class (Elem, Sext, Sextable)+import qualified Data.Sext.Class as C+import Data.Sext.TH+++-- | Safely create a Sext, possibly altering the source to match+-- target length. If target length is less than that of the source,+-- the source gets truncated. If target length is greater, the source+-- is padded using the provided basic element. Elements on the left+-- are preferred.+--+-- >>> createLeft ' ' "foobarbaz" :: Sext 6 String+-- "foobar"+-- >>> createLeft '#' "foobarbaz" :: Sext 12 String+-- "foobarbaz###"+createLeft :: forall a i.+ (Sextable a, KnownNat i) =>+ Elem a -> a -> Sext i a+createLeft e s =+ C.unsafeCreate $+ C.take (C.length s) $+ C.append s $+ C.replicate (t - C.length s) e+ where+ t = fromIntegral $ natVal (Proxy :: Proxy i)+++-- | Just like 'createLeft', except that elements on the right are preferred.+--+-- >>> createRight '@' "foobarbaz" :: Sext 6 String+-- "barbaz"+-- >>> createRight '!' "foobarbaz" :: Sext 12 String+-- "!!!foobarbaz"+createRight :: forall a i.+ (Sextable a, KnownNat i) =>+ Elem a -> a -> Sext i a+createRight e s =+ C.unsafeCreate $+ C.drop (C.length s - t) $+ C.append (C.replicate (t - C.length s) e) s+ where+ t = fromIntegral $ natVal (Proxy :: Proxy i)+++-- | Attempt to safely create a Sext if it matches target length.+--+-- >>> create "foobar" :: Maybe (Sext 6 String)+-- Just "foobar"+-- >>> create "barbaz" :: Maybe (Sext 8 String)+-- Nothing+--+-- This is safer than 'C.unsafeCreate' and unlike with 'createLeft' /+-- 'createRight' the source value is left unchanged. However, this+-- implies a further run-time check for Nothing values.+create :: forall a i.+ (Sextable a, KnownNat i) =>+ a -> P.Maybe (Sext i a)+create s =+ if C.length s == t+ then Just $ C.unsafeCreate s+ else Nothing+ where+ t = fromIntegral $ natVal (Proxy :: Proxy i)+++-- | Append two Sexts together.+--+-- >>> append "foo" "bar" :: Sext 6 String+-- "foobar"+append :: forall a m n.+ (Sextable a) => Sext m a -> Sext n a -> Sext (m + n) a+append a b = C.unsafeCreate $ C.append (C.unwrap a) (C.unwrap b)+++-- | Construct a new Sext from a basic element.+--+-- >>> replicate '=' :: Sext 10 String+-- "=========="+replicate :: forall a i.+ (Sextable a, KnownNat i) => Elem a -> Sext i a+replicate e =+ C.unsafeCreate $ C.replicate t e+ where+ t = fromIntegral $ natVal (Proxy :: Proxy i)+++map :: Sextable a =>+ (Elem a -> Elem a) -> Sext m a -> Sext m a+map f s =+ C.unsafeCreate $ C.map f $ C.unwrap s+++-- | Reduce Sext length, preferring elements on the left.+--+-- >>> take "Foobar" :: Sext 3 String+-- "Foo"+take :: forall a m n.+ (Sextable a, KnownNat m, KnownNat n, n <= m) =>+ Sext m a -> Sext n a+take s =+ C.unsafeCreate $ C.take t $ C.unwrap s+ where+ t = fromIntegral $ natVal (Proxy :: Proxy n)+++-- | Reduce Sext length, preferring elements on the right.+--+-- >>> drop "Foobar" :: Sext 2 String+-- "ar"+drop :: forall a m n.+ (Sextable a, KnownNat m, KnownNat n, n <= m) =>+ Sext m a -> Sext n a+drop s =+ C.unsafeCreate $ C.drop (C.length s' - t) s'+ where+ s' = C.unwrap s+ t = fromIntegral $ natVal (Proxy :: Proxy n)+++-- | Obtain value-level length.+length :: forall a m.+ KnownNat m => Sext m a -> P.Int+length _ = P.fromIntegral P.$ natVal (Proxy :: Proxy m)+++-- | Fill a Sext with extra elements up to target length, padding+-- original elements to the left.+padLeft :: forall a m n.+ (Sextable a, KnownNat m, KnownNat (n - m),+ n ~ (n - m + m), m <= n) =>+ Elem a -> Sext m a -> Sext n a+padLeft pad = append (replicate pad)+++-- | Like 'padLeft', but original elements are padded to the right.+padRight :: forall a m n.+ (Sextable a, KnownNat m, KnownNat (n - m),+ n ~ (m + (n - m)), m <= n) =>+ Elem a -> Sext m a -> Sext n a+padRight pad = P.flip append (replicate pad)
+ src/Data/Sext/Class.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}++{-|++Use this only if you need to make some type Sextable.++-}++module Data.Sext.Class+ ( Sextable(..)+ )++where++import Prelude+import qualified Prelude as P++#ifdef WITH_BS+import qualified Data.ByteString as B+import GHC.Word+#endif++#ifdef WITH_TEXT+import qualified Data.Text as T+#endif++import GHC.TypeLits+++-- | Class of types which can be assigned a type-level length.+class Sextable a where+ -- | Data family which wraps values of the underlying type giving+ -- them a type-level length. @Sext 6 t@ means a value of type @t@ of+ -- length 6.+ data Sext (i :: Nat) a++ -- | Basic element type. For @Sextable [a]@, this is @a@.+ type Elem a++ -- | Simply wrap a value in a Sext as is, assuming any length.+ --+ -- For example, an expression like+ --+ -- > unsafeCreate "somestring" :: Sext 50 String+ --+ -- will typecheck, although the stored length information will not+ -- match actual string size. This may result in wrong behaviour of+ -- all functions defined for Sext.+ --+ -- Use it only when you know what you're doing.+ --+ -- When implementing new Sextable instances, code this to simply+ -- apply the constructor of 'Sext'.+ unsafeCreate :: a -> Sext i a++ -- | Forget type-level length, obtaining the underlying value.+ unwrap :: Sext i a -> a++ length :: a -> Int+ append :: a -> a -> a+ replicate :: Int -> Elem a -> a+ map :: (Elem a -> Elem a) -> a -> a+ take :: Int -> a -> a+ drop :: Int -> a -> a+++instance (Show a, Sextable a) => Show (Sext i a) where+ show s = show $ unwrap s+++instance Sextable [a] where+ type Elem [a] = a++ data Sext i [a] = List [a]++ unsafeCreate = List+ unwrap (List l) = l++ length = P.length+ append = (P.++)+ replicate = P.replicate+ map = P.map+ take = P.take+ drop = P.drop+++#ifdef WITH_TEXT+instance Sextable T.Text where+ type Elem T.Text = Char++ data Sext i T.Text = Text T.Text++ unsafeCreate = Text+ unwrap (Text t) = t++ length = T.length+ append = T.append+ replicate = \n c -> T.replicate n (T.singleton c)+ map = T.map+ take = T.take+ drop = T.drop+#endif+++#ifdef WITH_BS+instance Sextable B.ByteString where+ type Elem B.ByteString = Word8++ data Sext i B.ByteString = ByteString B.ByteString++ unsafeCreate = ByteString+ unwrap (ByteString t) = t++ length = B.length+ append = B.append+ replicate = B.replicate+ map = B.map+ take = B.take+ drop = B.drop+#endif
+ src/Data/Sext/TH.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TemplateHaskell #-}++{-|++Template Haskell helpers for Sext.++-}++module Data.Sext.TH+ ( sext+ )++where++import Prelude+import qualified Prelude as P (length)++import Data.Sext.Class+import Data.String++import Language.Haskell.TH+++-- | A type with IsString instance to allow string literals in 'sext'+-- argument without quoting.+newtype LitS = LitS String deriving IsString+++-- | Type-safe Sext constructor macro for string literals.+--+-- Example:+--+-- > $(sext "Foobar")+--+-- compiles to+--+-- > unsafeCreate "Foobar" :: forall a. (IsString a, Sextable a) => Sext 6 a+--+-- where 6 is the string length obtained at compile time.+sext :: LitS -> Q Exp+sext (LitS s) =+ do+ at <- newName "a"+ return $ SigE (AppE (VarE 'unsafeCreate) (LitE $ StringL s))+ (ForallT+ [PlainTV at]+ [ ClassP ''IsString [VarT at]+ , ClassP ''Sextable [VarT at]] $+ (AppT+ (AppT+ (ConT ''Sext)+ (LitT $ NumTyLit (fromIntegral $ P.length s)))+ (VarT at)))