s-expression (empty) → 0.0.0
raw patch · 7 files changed
+477/−0 lines, 7 filesdep +basedep +derive-monoiddep +lenssetup-changed
Dependencies added: base, derive-monoid, lens, s-expression, semigroups
Files
- LICENSE +22/−0
- Main.hs +1/−0
- Setup.hs +2/−0
- s-expression.cabal +88/−0
- sources/Data/Sexp.hs +330/−0
- sources/Data/Sexp/Example.hs +19/−0
- sources/Data/Sexp/Main.hs +15/−0
+ LICENSE view
@@ -0,0 +1,22 @@+The MIT License (MIT)++Copyright (c) 2015 Sam Boosalis++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 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.+
+ Main.hs view
@@ -0,0 +1,1 @@+import Data.Sexp.Main
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ s-expression.cabal view
@@ -0,0 +1,88 @@+name: s-expression+version: 0.0.0+cabal-version: >=1.10+build-type: Simple+license: MIT+license-file: LICENSE+copyright: Copyright (C) 2015 Spiros M. Boosalis+maintainer: samboosalis@gmail.com+stability: experimental+homepage: https://github.com/sboosali/s-expression#readme+bug-reports: https://github.com/sboosali/s-expression/issues+synopsis: simple general-purpose s-expressions +description: + this package provides general-purpose functionality for manipulating s-expressions. like:+ . + * a @Functor@ instance that transforms the atoms+ . + * a @Monad@ instance that "expands" atoms into s-expressions+ . + * a @Foldable@ instance that enumerates the atoms (leaves)+ . + * @IsList@ and @IsString@ instances for literal syntax + . + * + . + * + . + * + . + the core type is:+ . + @+ data <https://hackage.haskell.org/package/s-expression/docs//Data-Sexp.html Sexp> f a+ = Atom a + | List [Sexp f a]+ | Sexp f [Sexp f a]+ @+ .+ which lets you provide your own custom function name that interprets its arguments.+ . + e.g. TODO + . + for efficient parsing/printing, use: + . + * https://hackage.haskell.org/package/sexp+ . + * https://hackage.haskell.org/package/atto-lisp+ . ++category: Data +author: Spiros Boosalis+tested-with: GHC ==7.10.1++source-repository head+ type: git+ location: https://github.com/sboosali/s-expression++library++ exposed-modules:+ Data.Sexp + Data.Sexp.Example + Data.Sexp.Main ++ build-depends:+ base >=4.8 && <5.0++ -- cabal sandbox add-source ... + , derive-monoid >=0.0.1 ++ , lens + , semigroups++ default-language: Haskell2010+ hs-source-dirs: sources+ ghc-options: -Wall+ default-extensions: AutoDeriveTypeable+++executable example-s-expressions+ main-is: Main.hs+ build-depends:+ base+ , s-expression+ default-language: Haskell2010+ hs-source-dirs: .+ ghc-options: -Wall+
+ sources/Data/Sexp.hs view
@@ -0,0 +1,330 @@+{-# LANGUAGE AutoDeriveTypeable, DeriveDataTypeable, DeriveGeneric, DeriveAnyClass, DeriveFunctor, DeriveFoldable, DeriveTraversable #-}+{-# LANGUAGE TemplateHaskell, LambdaCase, TypeFamilies #-}+module Data.Sexp where++import Derive.List (deriveList) +import Control.Lens (Plated (..)) ++import Data.Foldable (Foldable (..))+import Data.Data (Data) +import GHC.Generics (Generic) +import GHC.Exts (IsString (..))+import Data.Void (Void) +++{- | a heterogenous list.++a <http://en.wikipedia.org/wiki/Common_Lisp#The_function_namespace Lisp-2> S-expression, where:++* @f@ is the function namespace+* @a@ is the atom namespace++you could define @type Lisp1 a = Sexp a a@. with some caveats: ++* @f@ is ignored by 'Monad'ic methods like 'joinSexp'+* @plate@ doesn't reach the @f@, even when @f ~ a@, as the 'Plated' instance is manual, not automatic via @Data@.++the 'List' case is just a specialized @'Sexp' ()@, but easier to work with than:++* @Sexp (Maybe f) [Sexp f a]@ (where Nothing would represent 'List')+* forcing each concrete @f@ to hold a unit case (which would represent 'List')++examples: ++>>> 'toList' (List [Atom "f",Atom "x",List [Atom "g",Atom "y"],Atom "z"])+["f","x","g","y","z"]++>>> :{+let doubleSexp e = do+ x <- e+ listSexp [x,x]+:} +>>> doubleSexp (List [Atom "f", Sexp () [Atom "a", Atom "b"]])+List [List [Atom "f",Atom "f"],Sexp () [List [Atom "a",Atom "a"],List [Atom "b",Atom "b"]]]++-}+data Sexp f a+ = Atom a + | List [Sexp f a]+ | Sexp f [Sexp f a]+ deriving (Show,Read,Eq,Ord,Functor,Foldable,Traversable,Data,Generic)++{-| isomorphic to: ++@+data Sexp_ a+ = Atom_ a + | List_ [Sexp_ a]+@ ++when you only care about lists (e.g. to interface with other s-expression libraries). ++-}+type Sexp_ = Sexp Void ++{-| ++@+data ByteSexp+ = Atom ByteString+ | List [ByteSexp]++bytesexp2sexp :: ByteSexp -> 'Sexp_' ByteString+bytesexp2sexp = 'toSexp' $ \case+ Atom s -> Left s + List es -> Right es +@++-}+toSexp :: (r -> Either a [r]) -> (r -> Sexp_ a) +toSexp f = go+ where + go r = case f r of + Left a -> Atom a + Right rs -> List (map go rs) ++-- | default instance via the 'Monad' subclass.+instance Applicative (Sexp f) where+ pure = return + (<*>) f x = f >>= (\g -> x >>= (return.g))++{- |++definitions:++@+ 'return' = 'pureSexp'+ '(>>=)' = 'bindSexp'+@++proofs of laws:+++* left-inverse(1): @join . return = id@++ @+ join (return m)+ joinSexp (pureSexp m)+ joinSexp (Atom m)+ m+ @+++* left-inverse(2): @join . fmap return = id@++ (the Sexp case is elided, the steps being identical to the List case)++ @+ join (fmap return m)+ joinSexp (fmap pureSexp m)+ joinSexp (fmap Atom m)+ -- case analysis + case m of+ Atom x ->+ joinSexp (Atom (Atom x)) + -- by definition of joinSexp+ Atom x+ List ms ->+ joinSexp (List (fmap (fmap Atom) ms)+ -- by definition of joinSexp+ List (fmap joinSexp (fmap (fmap Atom) ms))+ -- functor composition + List (fmap (joinSexp . fmap Atom) ms)+ List (fmap (join . fmap return) ms)+ -- by induction+ List (fmap id ms)+ -- functor identity + List ms+ -- both cases are identity + m+ @+ + where: + + @+ fmap f = \case+ Atom x -> f x + List ms -> List (fmap (fmap f) ms)+ + join = \case+ Atom x -> x + List ms -> List (fmap joinSexp ms)+ @+ +* associativity(3): @join . join = join . fmap join@++ @+ TODO+ @++-}+instance Monad (Sexp f) where+ return = pure+ (>>=) = bindSexp+-- TODO laws, verified as @QuickCheck@ properties:++instance Plated (Sexp f a) where+ plate f = \case+ Atom a -> Atom <$> pure a+ List ps -> List <$> traverse f ps+ Sexp g ps -> Sexp g <$> traverse f ps++{-| ++>>> :set -XOverloadedStrings +>>> "x" :: Sexp f String+Atom "x" ++-}+instance (IsString a) => IsString (Sexp f a) where+ fromString = Atom . fromString++{-| @pureSexp = 'Atom'@+-}+pureSexp :: a -> Sexp f a+pureSexp = Atom+{-# INLINE pureSexp #-}++bindSexp :: Sexp f a -> (a -> Sexp f b) -> Sexp f b+bindSexp s f = (joinSexp . fmap f) s+{-# INLINE bindSexp #-}++{-| ++-}+joinSexp :: Sexp f (Sexp f a) -> Sexp f a+joinSexp = \case+ Atom e -> e+ List ess -> List (joinSexp <$> ess)+ Sexp f ess -> Sexp f (joinSexp <$> ess)+{-# INLINEABLE joinSexp #-} -- to hit the Atom I hope.++deriveList ''Sexp 'List ++{-| refines any Sexp to a list, which can be given to the 'List'. -}+toSexpList :: Sexp f a -> [Sexp f a]++{-| >>> appendSexp (Atom "f") (List [Atom "x"])+List [Atom "f",Atom "x"]+-}+appendSexp :: Sexp f a -> Sexp f a -> Sexp f a++{-| @emptySexp = 'List' []@ -}+emptySexp :: Sexp f a+++{-| fold over an sexp. ++i.e. strictly evaluate a sexp ("all the way") to an atom, within any monadic context.++-}+evalSexp :: (Monad m) => ([a] -> m a) -> ([a] -> g -> m a) -> Sexp g a -> m a+evalSexp list apply = \case+ Atom a -> pure a+ List es -> list =<< traverse go es+ Sexp g es -> (flip apply) g =<< traverse go es+ where+ go = evalSexp list apply++{-| ++>>> data ArithFunc = Add | Multiply | Negate deriving Show +>>> let badArith = Sexp Negate [Atom 1, Atom 2, Atom 3] :: Sexp ArithFunc Integer +>>> let goodArith = Sexp Add [Sexp Multiply [], Sexp Negate [Atom (10::Integer)], Sexp Multiply [Atom 2, Atom 3, Atom 4]]+>>> :set -XLambdaCase+>>> :{+let evalArith = \case+ Add -> \case+ xs -> Just [sum xs]+ Multiply -> \case+ xs -> Just [product xs]+ Negate -> \case+ [x] -> Just [negate x] + _ -> Nothing +:}+>>> evalSplatSexp (flip evalArith) (fmap (:[]) badArith) -- wrong arity +Nothing +>>> evalSplatSexp (flip evalArith) (fmap (:[]) goodArith) -- (+ (*) (- 10) (* 2 3 4))+Just [15] ++specializing, as above, @(m ~ Maybe)@, @(b ~ [Integer])@, @(g ~ ArithFunc)@:++@evalSplatSexp :: ([Integer] -> ArithFunc -> Maybe [Integer]) -> (Sexp ArithFunc [Integer] -> Maybe [Integer])@ ++@evalSplatSexp apply = 'evalSexp' ('pure'.'fold') (apply.'fold')@ ++-}+evalSplatSexp :: (Monad m, Monoid b) => (b -> g -> m b) -> (Sexp g b -> m b)+evalSplatSexp apply = evalSexp (pure.fold) (apply.fold)+{-# INLINE evalSplatSexp #-}++{-| ++when a Sexp\'s atoms are 'Monoid'al ("list-like"),+after evaluating some expressions into atoms,+we can "splat" them back together.++@splatList@ takes: ++* an evaluator @eval@++* and a list of s-expressions @es@ to evaluate in sequence.++-}+splatSexpList :: (Applicative m, Monoid b) => (Sexp g b -> m b) -> [Sexp g b] -> m b+splatSexpList eval = fmap fold . traverse eval +{-# INLINE splatSexpList #-}++{-| inject a list of atoms. ++>>> listSexp [1,2,3]+List [Atom 1,Atom 2,Atom 3]++-}+listSexp :: [a] -> Sexp f a+listSexp = List . map Atom +{-# INLINE listSexp #-}++-- data SexpF f a r+-- = AtomF a+-- | FuncF (f r) +-- | ListF [r]+-- deriving (Show,Read,Eq,Ord,Functor,Data,Generic)++-- type Sexp' a = Fix (SexpF a)++{- helper when converting from other sexp types, like from a parsing library. ++@Either Bytestring [Bytestring]@ ++is isomorphic to: ++e.g. the @atto-lisp@ package defines: ++@+data Lisp+ = Symbol Text -- ^ A symbol (including keyword)+ | String Text -- ^ A string.+ | Number Number -- ^ A number+ | List [Lisp] -- ^ A proper list: @(foo x 42)@+ | DotList [Lisp] Lisp -- ^ A list with a non-nil tail: @(foo x+ -- . 42)@. The list argument must be+ -- non-empty and the tail must be non-'nil'.+@ ++we can define: ++@+type AttoLispSexp = Sexp AttoLispFunc AttoLispAtom+data AttoLispAtom = SymbolAtom Text | StringAtom Text | NumberAtom Number +TODO data AttoLispFunc r = DotListFunc [r] r +@+++-}+-- fromSexp :: -> +-- fromSexp = ++-- toSexp :: -> +-- toSexp = +
+ sources/Data/Sexp/Example.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedLists, OverloadedStrings #-}+{-| (see source) ++-}+module Data.Sexp.Example where +import Data.Sexp +++exampleSexp :: Sexp () String+exampleSexp = ["f", "x", ["g", "y"], "z"]++-- type ElispSexp = Sexp ElispFunc ElispAtom++-- data ElispAtom+-- = ++-- data ElispFunc+-- = +
+ sources/Data/Sexp/Main.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE LambdaCase #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+module Data.Sexp.Main where +import Data.Sexp.Example +import Data.Foldable (Foldable (..))++import System.Environment (getArgs)+++main = mainWith =<< getArgs++mainWith = \case+ _ -> do + print$ exampleSexp + print$ toList exampleSexp