sexp-grammar-2.3.0: Invertible grammar combinators for S-expressions
Safe HaskellSafe
LanguageHaskell2010

Language.SexpGrammar

Description

Write your grammar once and get both parser and pretty-printer, for free.

import GHC.Generics
import Data.Text (Text)
import Language.SexpGrammar
import Language.SexpGrammar.Generic

data Person = Person
  { pName    :: Text
  , pAddress :: Text
  , pAge     :: Maybe Int
  } deriving (Show, Generic)

instance SexpIso Person where
  sexpIso = with $ \person ->  -- Person is isomorphic to:
    list (                           -- a list with
      el (sym "person") >>>          -- a symbol "person",
      el string         >>>          -- a string, and
      props (                        -- a property-list with
        "address" .:  string >>>     -- a keyword :address and a string value, and
        "age"     .:? int))  >>>     -- an optional keyword :age with int value.
    person

So now we can use this isomorphism to parse S-expessions to Person record and pretty-print Person records back to S-expression.

(person "John Doe" :address "42 Whatever str." :age 25)

will parse into:

Person {pName = "John Doe", pAddress = "42 Whatever str.", pAge = Just 25}

and the record will pretty-print back into:

(person "John Doe" :address "42 Whatever str." :age 25)
Synopsis

Data types

type Sexp = Fix (Compose (LocatedBy Position) SexpF) Source #

S-expression type annotated with positions. Useful for further parsing.

data Position Source #

Position: file name, line number, column number

Instances

Instances details
Eq Position Source # 
Instance details

Defined in Language.Sexp.Types

Ord Position Source # 
Instance details

Defined in Language.Sexp.Types

Show Position Source # 
Instance details

Defined in Language.Sexp.Types

Show Sexp Source # 
Instance details

Defined in Language.Sexp.Located

Methods

showsPrec :: Int -> Sexp -> ShowS #

show :: Sexp -> String #

showList :: [Sexp] -> ShowS #

Generic Position Source # 
Instance details

Defined in Language.Sexp.Types

Associated Types

type Rep Position :: Type -> Type #

Methods

from :: Position -> Rep Position x #

to :: Rep Position x -> Position #

NFData Position Source # 
Instance details

Defined in Language.Sexp.Types

Methods

rnf :: Position -> () #

Pretty Position Source # 
Instance details

Defined in Language.Sexp.Types

Methods

pretty :: Position -> Doc ann #

prettyList :: [Position] -> Doc ann #

NFData1 (Compose (LocatedBy Position) SexpF) Source # 
Instance details

Defined in Language.Sexp.Types

Methods

liftRnf :: (a -> ()) -> Compose (LocatedBy Position) SexpF a -> () #

type Rep Position Source # 
Instance details

Defined in Language.Sexp.Types

type Rep Position = D1 ('MetaData "Position" "Language.Sexp.Types" "sexp-grammar-2.3.0-JCnumEn9wOUGbR0nPzOSB9" 'False) (C1 ('MetaCons "Position" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 FilePath) :*: (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'SourceUnpack 'SourceStrict 'DecidedStrict) (Rec0 Int) :*: S1 ('MetaSel ('Nothing :: Maybe Symbol) 'SourceUnpack 'SourceStrict 'DecidedStrict) (Rec0 Int))))

type SexpGrammar a = forall t. Grammar Position (Sexp :- t) (a :- t) Source #

A common type of grammar that operates on S-expressions. This grammar accepts a single Sexp value and converts it into a value of type a.

data Grammar p a b #

Representation of an invertible grammar -- a grammar that can be run either "forwards" and "backwards".

For a grammar Grammar p a b, running it forwards will take a value of type a and possibly produce a value of type b. Running it backwards will take a value of type b and possibly produce an a. If a value cannot be produced, an error message is generated.

As a common example, running a Grammar forwards corresponds to parsing and running backwards corresponds to prettyprinting.

That is, the grammar defines a partial isomorphism between two values.

Instances

Instances details
Category (Grammar p :: Type -> Type -> Type) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

id :: forall (a :: k). Grammar p a a #

(.) :: forall (b :: k) (c :: k) (a :: k). Grammar p b c -> Grammar p a b -> Grammar p a c #

Semigroup (Grammar p a b) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

(<>) :: Grammar p a b -> Grammar p a b -> Grammar p a b #

sconcat :: NonEmpty (Grammar p a b) -> Grammar p a b #

stimes :: Integral b0 => b0 -> Grammar p a b -> Grammar p a b #

data h :- t infixr 5 #

"Cons" pair of a heterogenous list or a stack with potentially polymophic tail. E.g. "first" :- 2 :- (3,4) :- t

Isomorphic to a tuple with two elments, but is much more convenient for nested pairs.

Instances

Instances details
Bitraversable (:-) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

bitraverse :: Applicative f => (a -> f c) -> (b -> f d) -> (a :- b) -> f (c :- d) #

Bifoldable (:-) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

bifold :: Monoid m => (m :- m) -> m #

bifoldMap :: Monoid m => (a -> m) -> (b -> m) -> (a :- b) -> m #

bifoldr :: (a -> c -> c) -> (b -> c -> c) -> c -> (a :- b) -> c #

bifoldl :: (c -> a -> c) -> (c -> b -> c) -> c -> (a :- b) -> c #

Bifunctor (:-) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

bimap :: (a -> b) -> (c -> d) -> (a :- c) -> b :- d #

first :: (a -> b) -> (a :- c) -> b :- c #

second :: (b -> c) -> (a :- b) -> a :- c #

Functor ((:-) h) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

fmap :: (a -> b) -> (h :- a) -> h :- b #

(<$) :: a -> (h :- b) -> h :- a #

Foldable ((:-) h) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

fold :: Monoid m => (h :- m) -> m #

foldMap :: Monoid m => (a -> m) -> (h :- a) -> m #

foldMap' :: Monoid m => (a -> m) -> (h :- a) -> m #

foldr :: (a -> b -> b) -> b -> (h :- a) -> b #

foldr' :: (a -> b -> b) -> b -> (h :- a) -> b #

foldl :: (b -> a -> b) -> b -> (h :- a) -> b #

foldl' :: (b -> a -> b) -> b -> (h :- a) -> b #

foldr1 :: (a -> a -> a) -> (h :- a) -> a #

foldl1 :: (a -> a -> a) -> (h :- a) -> a #

toList :: (h :- a) -> [a] #

null :: (h :- a) -> Bool #

length :: (h :- a) -> Int #

elem :: Eq a => a -> (h :- a) -> Bool #

maximum :: Ord a => (h :- a) -> a #

minimum :: Ord a => (h :- a) -> a #

sum :: Num a => (h :- a) -> a #

product :: Num a => (h :- a) -> a #

Traversable ((:-) h) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

traverse :: Applicative f => (a -> f b) -> (h :- a) -> f (h :- b) #

sequenceA :: Applicative f => (h :- f a) -> f (h :- a) #

mapM :: Monad m => (a -> m b) -> (h :- a) -> m (h :- b) #

sequence :: Monad m => (h :- m a) -> m (h :- a) #

(Eq h, Eq t) => Eq (h :- t) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

(==) :: (h :- t) -> (h :- t) -> Bool #

(/=) :: (h :- t) -> (h :- t) -> Bool #

(Show h, Show t) => Show (h :- t) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

showsPrec :: Int -> (h :- t) -> ShowS #

show :: (h :- t) -> String #

showList :: [h :- t] -> ShowS #

class SexpIso a where Source #

A class for types that could be converted to and inferred from s-expressions defined by Sexp.

Instances

Instances details
SexpIso Bool Source # 
Instance details

Defined in Language.SexpGrammar.Class

SexpIso Double Source # 
Instance details

Defined in Language.SexpGrammar.Class

SexpIso Int Source # 
Instance details

Defined in Language.SexpGrammar.Class

SexpIso Integer Source # 
Instance details

Defined in Language.SexpGrammar.Class

SexpIso () Source # 
Instance details

Defined in Language.SexpGrammar.Class

SexpIso Text Source # 
Instance details

Defined in Language.SexpGrammar.Class

SexpIso Scientific Source # 
Instance details

Defined in Language.SexpGrammar.Class

SexpIso a => SexpIso [a] Source # 
Instance details

Defined in Language.SexpGrammar.Class

Methods

sexpIso :: SexpGrammar [a] Source #

SexpIso a => SexpIso (Maybe a) Source # 
Instance details

Defined in Language.SexpGrammar.Class

SexpIso a => SexpIso (NonEmpty a) Source # 
Instance details

Defined in Language.SexpGrammar.Class

(Ord a, SexpIso a) => SexpIso (Set a) Source # 
Instance details

Defined in Language.SexpGrammar.Class

(SexpIso a, SexpIso b) => SexpIso (Either a b) Source # 
Instance details

Defined in Language.SexpGrammar.Class

(SexpIso a, SexpIso b) => SexpIso (a, b) Source # 
Instance details

Defined in Language.SexpGrammar.Class

Methods

sexpIso :: SexpGrammar (a, b) Source #

(Ord k, SexpIso k, SexpIso v) => SexpIso (Map k v) Source # 
Instance details

Defined in Language.SexpGrammar.Class

Methods

sexpIso :: SexpGrammar (Map k v) Source #

(SexpIso a, SexpIso b, SexpIso c) => SexpIso (a, b, c) Source # 
Instance details

Defined in Language.SexpGrammar.Class

Methods

sexpIso :: SexpGrammar (a, b, c) Source #

(SexpIso a, SexpIso b, SexpIso c, SexpIso d) => SexpIso (a, b, c, d) Source # 
Instance details

Defined in Language.SexpGrammar.Class

Methods

sexpIso :: SexpGrammar (a, b, c, d) Source #

(SexpIso a, SexpIso b, SexpIso c, SexpIso d, SexpIso e) => SexpIso (a, b, c, d, e) Source # 
Instance details

Defined in Language.SexpGrammar.Class

Methods

sexpIso :: SexpGrammar (a, b, c, d, e) Source #

(SexpIso a, SexpIso b, SexpIso c, SexpIso d, SexpIso e, SexpIso f) => SexpIso (a, b, c, d, e, f) Source # 
Instance details

Defined in Language.SexpGrammar.Class

Methods

sexpIso :: SexpGrammar (a, b, c, d, e, f) Source #

(SexpIso a, SexpIso b, SexpIso c, SexpIso d, SexpIso e, SexpIso f, SexpIso g) => SexpIso (a, b, c, d, e, f, g) Source # 
Instance details

Defined in Language.SexpGrammar.Class

Methods

sexpIso :: SexpGrammar (a, b, c, d, e, f, g) Source #

Encoding

toSexp :: SexpGrammar a -> a -> Either String Sexp Source #

Run grammar in generating (right-to-left) direction

toSexp g = runGrammarString Sexp.dummyPos . backward (sealed g)

encode :: SexpIso a => a -> Either String ByteString Source #

Serialize a value using SexpIso instance

encodeWith :: SexpGrammar a -> a -> Either String ByteString Source #

Serialise a value using a provided grammar

encodePretty :: SexpIso a => a -> Either String ByteString Source #

Serialise and pretty-print a value using its SexpIso instance

encodePrettyWith :: SexpGrammar a -> a -> Either String ByteString Source #

Serialise and pretty-print a value using a provided grammar

Decoding

fromSexp :: SexpGrammar a -> Sexp -> Either String a Source #

Run grammar in parsing (left-to-right) direction

fromSexp g = runGrammarString Sexp.dummyPos . forward (sealed g)

decode :: SexpIso a => ByteString -> Either String a Source #

Deserialise a value using its SexpIso instance

decodeWith :: SexpGrammar a -> FilePath -> ByteString -> Either String a Source #

Deserialise a value using provided grammar, use a provided file name for error messages

Combinators

(>>>) :: forall k cat (a :: k) (b :: k) (c :: k). Category cat => cat a b -> cat b c -> cat a c infixr 1 #

Left-to-right composition

(<<<) :: forall k cat (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c infixr 1 #

Right-to-left composition

position :: Grammar Position (Sexp :- t) (Position :- (Sexp :- t)) Source #

Extract/inject a position from/to a Sexp.

Atoms

real :: Grammar Position (Sexp :- t) (Scientific :- t) Source #

Grammar matching fractional number atoms to Scientific values.

>>> encodeWith real (3.141592653589793^3)
Right "31.006276680299813114880451174049119330924860257"

double :: Grammar Position (Sexp :- t) (Double :- t) Source #

Grammar matching fractional number atoms to Double values.

>>> encodeWith double (3.141592653589793^3)
Right "31.006276680299816"

int :: Grammar Position (Sexp :- t) (Int :- t) Source #

Grammar matching integer number atoms to Int values.

>>> encodeWith int (2^63)
Right "-9223372036854775808"
>>> encodeWith int (2^63-1)
Right "9223372036854775807"

integer :: Grammar Position (Sexp :- t) (Integer :- t) Source #

Grammar matching integer number atoms to Integer values.

>>> encodeWith integer (2^100)
Right "1267650600228229401496703205376"

string :: Grammar Position (Sexp :- t) (Text :- t) Source #

Grammar matching string literal atoms to Text values.

>>> let grammar = list (el string >>> el int) >>> pair
>>> encodeWith grammar ("some-string", 42)
Right "(\"some-string\" 42)"

symbol :: Grammar Position (Sexp :- t) (Text :- t) Source #

Grammar matching symbol literal atoms to Text values.

>>> encodeWith symbol "some-symbol"
Right "some-symbol"

keyword :: Grammar Position (Sexp :- t) (Text :- t) Source #

Grammar matching symbol literal atoms starting with ':' to Text values without the colon char.

>>> encodeWith keyword "username"
Right ":username"

sym :: Text -> Grammar Position (Sexp :- t) t Source #

Grammar matching symbol literal atoms to a specified symbol.

>>> let grammar = list (el (sym "username") >>> el string)
>>> encodeWith grammar "Julius Caesar"
Right "(username \"Julius Caesar\")"

kwd :: Text -> Grammar Position (Sexp :- t) t Source #

Grammar matching symbol literal atoms to a specified symbol prepended with ':'.

>>> let grammar = list (el (kwd "password") >>> el int)
>>> encodeWith grammar 42
Right "(:password 42)"

Lists

data List Source #

Elements of a list that is being parsed/constructed.

list :: Grammar Position (List :- t) (List :- t') -> Grammar Position (Sexp :- t) t' Source #

Parenthesis list grammar. Runs a specified grammar on a sequence of S-exps in a parenthesized list.

>>> let grammar = list (el symbol >>> el int) >>> pair
>>> encodeWith grammar ("foo", 42)
Right "(foo 42)"

bracketList :: Grammar Position (List :- t) (List :- t') -> Grammar Position (Sexp :- t) t' Source #

Bracket list grammar. Runs a specified grammar on a sequence of S-exps in a bracketed list.

>>> let grammar = bracketList (rest int)
>>> encodeWith grammar [2, 3, 5, 7, 11, 13]
Right "[2 3 5 7 11 13]"

braceList :: Grammar Position (List :- t) (List :- t') -> Grammar Position (Sexp :- t) t' Source #

Brace list grammar. Runs a specified grammar on a sequence of S-exps in a list enclosed in braces.

>>> let grammar = braceList (props (key "x" real >>> key "y" real)) >>> pair
>>> encodeWith grammar (3.1415, -1)
Right "{:x 3.1415 :y -1}"

el :: Grammar Position (Sexp :- t) t' -> Grammar Position (List :- t) (List :- t') Source #

Element of a sequence grammar. Runs a specified grammar on a next element of a sequence. The underlying grammar can produce zero or more values on the stack.

E.g.:

  • el (sym "lambda") consumes a symbol "lambda" and produces no values on the stack.
  • el symbol consumes a symbol and produces a Text value corresponding to the symbol.

rest :: (forall t. Grammar Position (Sexp :- t) (a :- t)) -> Grammar Position (List :- t) (List :- ([a] :- t)) Source #

The rest of a sequence grammar. Runs a specified grammar on each of remaining elements of a sequence and collect them. Expects zero or more elements in the sequence.

>>> let grammar = list (el (sym "check-primes") >>> rest int)
>>> encodeWith grammar [2, 3, 5, 7, 11, 13]
Right "(check-primes 2 3 5 7 11 13)"

Property lists

data PropertyList Source #

Key/value pairs of a property list that is being parsed/constructed.

props :: Grammar Position (PropertyList :- t) (PropertyList :- t') -> Grammar Position (List :- t) (List :- t') Source #

Property list in a sequence grammar. Collects pairs of keywords and S-expressions from remaining sequence elements and runs a specified grammar on them. Expects zero or more pairs in the sequence. If sequence of pairs interrupts with a non-keyword, the rest of this sequence is left untouched.

Collected PropertyList is then available for random-access lookup combinators key, optKey, .:, .:? or bulk extraction restKeys combinator.

>>> :{
 let grammar = braceList (
       props (key "real" real >>> key "img" real) >>> onTail pair >>> el (sym "/") >>>
       props (key "real" real >>> key "img" real) >>> onTail pair) >>> pair
 in encodeWith grammar ((0, -1), (1, 0))
:}
Right "{:real 0 :img -1 / :real 1 :img 0}"

key :: Text -> (forall t. Grammar Position (Sexp :- t) (a :- t)) -> Grammar Position (PropertyList :- t) (PropertyList :- (a :- t)) Source #

Property by a key grammar. Looks up an S-expression by a specified key and runs a specified grammar on it. Expects the key to be present.

Note: performs linear lookup, O(n)

optKey :: Text -> (forall t. Grammar Position (Sexp :- t) (a :- t)) -> Grammar Position (PropertyList :- t) (PropertyList :- (Maybe a :- t)) Source #

Optional property by a key grammar. Like key but puts Nothing in correspondence to the missing key and Just to the present.

Note: performs linear lookup, O(n)

(.:) :: Text -> (forall t. Grammar Position (Sexp :- t) (a :- t)) -> Grammar Position (PropertyList :- t) (PropertyList :- (a :- t)) infix 3 Source #

Property by a key grammar. Infix version of key.

(.:?) :: Text -> (forall t. Grammar Position (Sexp :- t) (a :- t)) -> Grammar Position (PropertyList :- t) (PropertyList :- (Maybe a :- t)) infix 3 Source #

Optional property by a key grammar. Infix version of optKey.

restKeys :: (forall t. Grammar Position (Sexp :- (Text :- t)) (a :- t)) -> Grammar Position (PropertyList :- t) (PropertyList :- ([a] :- t)) Source #

Remaining properties grammar. Extracts all key-value pairs and applies a grammar on every element.

Quotes, antiquotes, etc

data Prefix Source #

S-expression quotation type

Constructors

Quote 
Backtick 
Comma 
CommaAt 
Hash 

Instances

Instances details
Eq Prefix Source # 
Instance details

Defined in Language.Sexp.Types

Methods

(==) :: Prefix -> Prefix -> Bool #

(/=) :: Prefix -> Prefix -> Bool #

Ord Prefix Source # 
Instance details

Defined in Language.Sexp.Types

Show Prefix Source # 
Instance details

Defined in Language.Sexp.Types

Generic Prefix Source # 
Instance details

Defined in Language.Sexp.Types

Associated Types

type Rep Prefix :: Type -> Type #

Methods

from :: Prefix -> Rep Prefix x #

to :: Rep Prefix x -> Prefix #

NFData Prefix Source # 
Instance details

Defined in Language.Sexp.Types

Methods

rnf :: Prefix -> () #

type Rep Prefix Source # 
Instance details

Defined in Language.Sexp.Types

type Rep Prefix = D1 ('MetaData "Prefix" "Language.Sexp.Types" "sexp-grammar-2.3.0-JCnumEn9wOUGbR0nPzOSB9" 'False) ((C1 ('MetaCons "Quote" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Backtick" 'PrefixI 'False) (U1 :: Type -> Type)) :+: (C1 ('MetaCons "Comma" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "CommaAt" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Hash" 'PrefixI 'False) (U1 :: Type -> Type))))

prefixed :: Prefix -> Grammar Position (Sexp :- t) t' -> Grammar Position (Sexp :- t) t' Source #

Grammar matching a prefixed S-expression, runs a sub-grammar on a Sexp under the prefix.

>>> encodeWith (prefixed Backtick symbol) "foo"
Right "`foo"

quoted :: Grammar Position (Sexp :- t) t' -> Grammar Position (Sexp :- t) t' Source #

Grammar matching a prefixed S-expression, runs a sub-grammar on a Sexp under the quotation.

>>> encodeWith (quoted symbol) "foo"
Right "'foo"

hashed :: Grammar Position (Sexp :- t) t' -> Grammar Position (Sexp :- t) t' Source #

Grammar matching a prefixed S-expression, runs a sub-grammar on a Sexp under the hash prefix.

>>> encodeWith (hashed symbol) "foo"
Right "#foo"

Error reporting

data Mismatch #

Data type to encode mismatches during parsing or generation, kept abstract. Use expected and unexpected constructors to build a mismatch report.

Instances

Instances details
Eq Mismatch 
Instance details

Defined in Data.InvertibleGrammar.Monad

Show Mismatch 
Instance details

Defined in Data.InvertibleGrammar.Monad

Semigroup Mismatch 
Instance details

Defined in Data.InvertibleGrammar.Monad

Monoid Mismatch 
Instance details

Defined in Data.InvertibleGrammar.Monad

expected :: Text -> Mismatch #

Construct a mismatch report with specified expectation. Can be appended to other expectations and unexpected reports to clarify a mismatch.

unexpected :: Text -> Mismatch #

Construct a mismatch report with information what occurred during the processing but was not expected.