sexp-grammar-2.1.0: Invertible grammar combinators for S-expressions

Safe HaskellNone
LanguageHaskell2010

Language.SexpGrammar

Contents

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
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 #

NFData (Fix (Compose (LocatedBy Position) SexpF)) Source # 
Instance details

Defined in Language.Sexp.Types

Methods

rnf :: Fix (Compose (LocatedBy Position) SexpF) -> () #

type Rep Position Source # 
Instance details

Defined in Language.Sexp.Types

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
Category (Grammar p :: Type -> Type -> Type) 
Instance details

Defined in Data.InvertibleGrammar.Base

Methods

id :: Grammar p a a #

(.) :: 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
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 #

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
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 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 #

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

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

Left-to-right composition

(<<<) :: 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
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.1.0-HOaCUN1gmzAB0sWp9Gn5Df" 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) (a :- t) -> Grammar Position (Sexp :- t) (a :- 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) (a :- t) -> Grammar Position (Sexp :- t) (a :- 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) (a :- t) -> Grammar Position (Sexp :- t) (a :- 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.

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.