Slide 1

Slide 1 text

Parsing and pretty printing with prisms Fraser Tweedale @hackuador April 29, 2016

Slide 2

Slide 2 text

Intro

Slide 3

Slide 3 text

Background ASN.1 DER is “special” Existing libraries fell short

Slide 4

Slide 4 text

Assumed knowledge Haskell syntax “Traditional” functional parser combinators

Slide 5

Slide 5 text

Prism primer ghci> :t _Right _Right :: Prism (Either c a) (Either c b) a b ghci> preview _Right (Right 42) Just 42 ghci> preview _Right (Left "nope") Nothing ghci> review _Right 42 Right 42 ghci> preview (_Left . _Right) (Left (Right 42)) Just 42

Slide 6

Slide 6 text

ASN.1 and DER BasicConstraints ::= SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLenConstraint INTEGER (0..MAX) OPTIONAL }

Slide 7

Slide 7 text

Data.X509.Ext instance Extension ExtBasicConstraints where extDecode [Start Sequence,End Sequence] = ... extDecode [Start Sequence,Boolean b,End Sequence] = ... extDecode [Start Sequence,Boolean b,IntVal v,End Sequence] = ... extDecode _ = Left "unknown sequence"

Slide 8

Slide 8 text

Parsers

Slide 9

Slide 9 text

Parser newtype Parser a = Parser { runParser :: String -> Maybe (a, String) } char :: Parser Char char = Parser $ \s -> case s of "" -> Nothing (c : s ) -> Just (c, s )

Slide 10

Slide 10 text

What if. . . input type is not String? element type is not Char?

Slide 11

Slide 11 text

Control.Lens.Cons uncons :: (Cons s s a a) => s -> Maybe (a, s) -- look familiar?

Slide 12

Slide 12 text

Control.Lens.Cons class Cons s t a b where _Cons :: Prism s t (a, s) (b, t) instance Cons ByteString ByteString Word8 Word8 instance Cons Text Text Char Char instance Cons [a] [b] a b instance Cons (Vector a) (Vector b) a b -- and many more!

Slide 13

Slide 13 text

Redefining the parser type Parser s a = Prism s s (a, s) (a, s) char :: (Cons s s a a) => Parser s a char = _Cons runParser :: Parser s a -> s -> Maybe (a, s) runParser = preview

Slide 14

Slide 14 text

But if the parser is a Prism. . . type Parser s a = Prism s s (a, s) (a, s) char :: (Cons s s a a) => Parser s a char = _Cons runParser :: Parser s a -> s -> Maybe (a, s) runParser = preview runPrinter :: Parser s a -> (a, s) -> s runPrinter = review

Slide 15

Slide 15 text

Parser ∧ printer → grammar type Grammar s a = Prism s s (a, s) (a, s) element :: (Cons s s a a) => Grammar s a element = _Cons runParser :: Grammar s a -> s -> Maybe (a, s) runParser = preview runPrinter :: Grammar s a -> (a, s) -> s runPrinter = review

Slide 16

Slide 16 text

Convenience functions parse :: Grammar s a -> s -> Maybe a parse g s = fmap fst (preview g s) print :: (Monoid s) => Grammar s a -> a -> s print g a = review g (a, mempty)

Slide 17

Slide 17 text

Grammar - map (<<$>>) :: Prism a a b b -> Grammar s a -> Grammar s b p <<$>> g = g . swapped . aside p . swapped swapped :: Iso (a, b) (c, d) (b, a) (d, c) aside :: Prism s t a b -> Prism (e, s) (e, t) (e, a) (e, b)

Slide 18

Slide 18 text

Grammar - product (<<*>>) :: Grammar s a -> Grammar s b -> Grammar s (a, b) g1 <<*>> g2 = g1 . aside g2 . productIso where productIso = iso (\(a, (b, s)) -> ((a, b), s)) (\((a, b), s) -> (a, (b, s)))

Slide 19

Slide 19 text

Grammar - sum (<<+>>) :: Grammar s a -> Grammar s b -> Grammar s (Either a b) g1 <<+>> g2 = prism (\(x, s) -> either (review g1 . (,s)) (review g2 . (,s)) x) (\s -> first Left <$> preview g1 s <|> first Right <$> preview g2 s)

Slide 20

Slide 20 text

Grammar - list many :: Grammar s a -> Grammar s [a] many g = isoList <<$>> (g <<*>> many g) <<+>> success () isoList :: Iso (Either (a, [a]) ()) [a] isoList = ... -- like pure :: Applicative f => a -> f a success :: a -> Grammar s a success a = prism snd (Just . (a,))

Slide 21

Slide 21 text

Grammar - basic grammars satisfy :: (Cons s s a a) => (a -> Bool) -> Grammar s a satisfy f = prism id (\a -> guard (f a) >> pure a) <<$>> element symbol :: (Cons s s a a, Eq a) => a -> Grammar s a symbol a = satisfy (== a) eof :: (Cons s s a a) => Grammar s () eof = prism snd (\s -> maybe (Just ((), s)) (const Nothing) (uncons s))

Slide 22

Slide 22 text

Grammar - delimiters literal :: (Cons s s a a, Eq a) => a -> Grammar s () match :: Grammar s a -> a -> Grammar s () between :: Grammar s () -> Grammar s () -> Grammar s a -> Grammar s a

Slide 23

Slide 23 text

Grammar - combinators (<<*) :: Grammar s a -> Grammar s () -> Grammar s a (*>>) :: Grammar s () -> Grammar s a -> Grammar s a many1 :: Grammar s a -> Grammar s (NonEmpty a) replicate :: Natural -> Grammar s a -> Grammar s [a]

Slide 24

Slide 24 text

Grammar - bind bind :: Grammar s a -> (a -> Grammar s b) -> (b -> a) -> Grammar s b bind p f g = prism (\(b, s) -> review p (g b, review (f (g b)) (b, s))) (preview p >=> \(a, s ) -> preview (f a) s )

Slide 25

Slide 25 text

Grammar - bind ghci> let g = bind integral (\n -> replicate n alpha)) length ghci> parse (many g) "1a2ab3abc4bad!" Just ["a","ab","abc"] ghci> print (many g) ["hello", "world"] :: String "5hello5world"

Slide 26

Slide 26 text

Deriving Isos for custom types {-# LANGUAGE TemplateHaskell #-} import Data.Fresnel.TH (makeIso) data Foo = A Int Char | B Bool makeIso Foo -- results in splice -- _Foo :: Iso (Either (Int, Char) Bool) Foo _Foo = ...

Slide 27

Slide 27 text

Putting it all together data PhoneNumber = PhoneNumber { areaCode :: String , localNumber :: String } deriving (Show) makeIso PhoneNumber phoneNumber :: Cons s s Char Char => Grammar s PhoneNumber phoneNumber = _PhoneNumber <<$>> between (literal ( ) (literal ) ) (replicate 2 digit) <<* match (many space) " " <<*>> replicate 8 (match (many space) "" *>> digit)

Slide 28

Slide 28 text

Putting it all together ghci> parse phoneNumber ("(07)3456 78 9 0" :: String) Just (PhoneNumber "07" "34567890") ghci> print phoneNumber (PhoneNumber "07" "34567890") :: String "(07) 34567890"

Slide 29

Slide 29 text

ASN.1 grammars

Slide 30

Slide 30 text

Primitive types boolean :: (Cons s s ASN1 ASN1) => Grammar s Bool integer :: (Cons s s ASN1 ASN1) => Grammar s Integer octetString :: (Cons s s ASN1 ASN1) => Grammar s B.ByteString oid :: (Cons s s ASN1 ASN1) => Grammar s OID

Slide 31

Slide 31 text

OPTIONAL, DEFAULT and SEQUENCE opt :: Grammar s a -> Grammar s (Maybe a) def :: (Eq a) => a -> Grammar s a -> Grammar s a sequence :: (Cons s s ASN1 ASN1) => Grammar s a -> Grammar s a

Slide 32

Slide 32 text

ASN.1 grammar - example data BasicConstraints = NotCA | CA (Maybe Natural) _BasicConstraints :: Iso (Bool, Maybe Natural) BasicConstraints _BasicConstraints = ... basicConstraints :: (Cons s s ASN1 ASN1) => Grammar s BasicConstraints basicConstraints = _BasicConstraints <<$>> sequence ( def False boolean <<*>> opt (natural <<$>> integer) )

Slide 33

Slide 33 text

Wrapping up

Slide 34

Slide 34 text

How did I get here?

Slide 35

Slide 35 text

s/standards/libraries/g CC-BY-NC 2.5 https://xkcd.com/927/

Slide 36

Slide 36 text

How did I get here? 1. Evaluate existing solutions

Slide 37

Slide 37 text

How did I get here? 1. Evaluate existing solutions 2. Write Cons-based parser (to parse [ASN1])

Slide 38

Slide 38 text

How did I get here? 1. Evaluate existing solutions 2. Write Cons-based parser (to parse [ASN1]) 3. Hmm. . . _Cons is a prism. . .

Slide 39

Slide 39 text

How did I get here? 1. Evaluate existing solutions 2. Write Cons-based parser (to parse [ASN1]) 3. Hmm. . . _Cons is a prism. . . 4. Type tetris

Slide 40

Slide 40 text

How did I get here? 1. Evaluate existing solutions 2. Write Cons-based parser (to parse [ASN1]) 3. Hmm. . . _Cons is a prism. . . 4. Type tetris 5. fresnel is born

Slide 41

Slide 41 text

fresnel and fresnel-asn1 Repos https://github.com/frasertweedale/hs-fresnel https://github.com/frasertweedale/hs-fresnel-asn1 AGPLv3+ I have actually used it!

Slide 42

Slide 42 text

Limitations and caveats Error reporting Performance

Slide 43

Slide 43 text

Advantages One module; many stream and element types Don’t repeat yourself Reuse existing optics ASN.1 grammar corresponds to spec

Slide 44

Slide 44 text

What’s next? Hackage Kerberos, X.509 Implement binary DER decoding?

Slide 45

Slide 45 text

Resources and related topics Invertible Syntax Descriptions Paper: www.informatik.uni-marburg.de/~rendel/unparse/ Libraries: boomerang, roundtrip, invertible-syntax Christian Marie’s YLJ2015 talk Experiment in combining Applicative and Divisible https://github.com/charleso/portmanteau lens

Slide 46

Slide 46 text

Fin © 2016 Fraser Tweedale Except where otherwise noted this work is licensed under http://creativecommons.org/licenses/by/4.0/ Slides https://github.com/ frasertweedale/talks/ Email frase@frase.id.au Twitter @hackuador