Skip to content

Commit

Permalink
Implemented RISON serialization.
Browse files Browse the repository at this point in the history
  • Loading branch information
martinvlk committed Jun 7, 2016
1 parent 04bd9f6 commit 02fde77
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 15 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# rison-hs
Haskel library for parsing and rendering RISON strings.
Haskell library for parsing and rendering [RISON](https://github.com/Nanonid/rison) strings.

The parsing part is functional, the rison string gets parsed into Aeson
Value objects.
Rison gets parsed into and serialized from Aeson [Value](http://hackage.haskell.org/package/aeson-0.11.2.0/docs/Data-Aeson-Types.html#t:Value) objects.

The rendering part is not yet implemented.
Implementation partly inspired by [Aeson](https://github.com/bos/aeson).

See the RISON spec at https://github.com/Nanonid/rison
5 changes: 5 additions & 0 deletions rison.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ library
hs-source-dirs: src
exposed-modules: Data.Rison
other-modules: Data.Rison.Parser
Data.Rison.Writer
build-depends: base >= 4.7 && < 5
, attoparsec
, parsers
Expand All @@ -26,6 +27,8 @@ library
, unordered-containers
, strings
, vector
, scientific
, text-format
default-language: Haskell2010

source-repository head
Expand All @@ -47,3 +50,5 @@ test-suite tests
, vector
, hspec
, rison
, scientific
, text-format
9 changes: 7 additions & 2 deletions src/Data/Rison.hs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
module Data.Rison (
decode
decode
, encode
) where

import Data.Aeson ( Value(..) )
import qualified Data.Attoparsec.ByteString as A
import Data.Attoparsec.ByteString.Char8 ( Parser )
import Data.ByteString ( ByteString )
import Data.Rison.Parser
import Data.Rison.Writer

decode :: ByteString -> Either String Value
decode input = A.parseOnly rison input
decode = A.parseOnly rison

encode :: Value -> ByteString
encode = write
6 changes: 3 additions & 3 deletions src/Data/Rison/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ objectValues = do

array :: Parser Value
array = do
v <- (A.word8 EXCLAMATION *>
A.word8 OPEN_BRACKET *> arrayValues <* A.word8 CLOSE_BRACKET)
v <- A.word8 EXCLAMATION *>
A.word8 OPEN_BRACKET *> arrayValues <* A.word8 CLOSE_BRACKET
return $ Array v

arrayValues :: Parser (V.Vector Value)
Expand Down Expand Up @@ -107,7 +107,7 @@ nulll :: Parser Value
nulll = do
A.word8 EXCLAMATION
A.word8 C_n
return $ Null
return Null

rstring :: Parser Value
rstring = do
Expand Down
50 changes: 50 additions & 0 deletions src/Data/Rison/Writer.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{-# LANGUAGE OverloadedStrings #-}

module Data.Rison.Writer ( write ) where

import Data.Aeson ( Value(..) )
import Data.ByteString ( ByteString )
import qualified Data.HashMap.Strict as H
import qualified Data.List as L
import Data.Maybe ( fromMaybe )
import Data.Monoid ( (<>) )
import Data.Scientific ( toRealFloat )
import qualified Data.Text as T
import Data.Text.Encoding ( encodeUtf8
, decodeUtf8 )
import Data.Text.Format ( Only(..)
, format
, shortest )
import Data.Text.Internal.Builder ( toLazyText )
import qualified Data.Text.Lazy as LT
import qualified Data.Vector as V

write :: Value -> ByteString
write Null = "!n"
write (Bool True) = "!t"
write (Bool False) = "!f"
write (Number n) = encodeUtf8 . LT.toStrict . toLazyText . shortest $ n

write (String s) = quot <> encodeUtf8 (T.foldr esc "" s) <> quot
where
esc c acc | c == '!' = "!!" <> acc
| c == '\\' = "!\\" <> acc
| otherwise = c `T.cons` acc

quot = if T.null . T.filter escChars $ s
then ""
else "'"

escChars '\\' = True
escChars '!' = True
escChars _ = False

write (Object m) = encodeUtf8 $
"(" <> T.intercalate "," (fmap pair sortedKeys) <> ")"
where
sortedKeys = L.sort $ H.keys m
pair k = k <> ":" <> (decodeUtf8 . write . fromMaybe "" . H.lookup k $ m)

write (Array v) = encodeUtf8 $
"!(" <> T.intercalate ","
(decodeUtf8 . write <$> V.toList v) <> ")"
59 changes: 54 additions & 5 deletions test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ main = hspec $ do
(decode "_ah-oj/y") `shouldBe` (Right $ String "_ah-oj/y")

context "object" $ do
it "empty object braced" $ do
it "empty" $ do
(decode "()") `shouldBe` (Right $ Object H.empty)
it "simple object braced" $ do
it "simple" $ do
(decode "(property:!n)") `shouldBe`
(Right $ Object $ H.fromList [("property", Null)])
it "simple object unquoted string" $ do
it "simple with unquoted string" $ do
(decode "(property:Off)") `shouldBe`
(Right $ Object $ H.fromList [("property", String "Off")])

context "array" $ do
it "empty array braced" $ do
it "empty" $ do
(decode "!()") `shouldBe` (Right $ Array V.empty)
it "booleans" $ do
(decode "!(!t,!t,!f,!t,!f)") `shouldBe`
Expand All @@ -53,7 +53,6 @@ main = hspec $ do
, Bool False
, Bool True
, Bool False])

context "numbers" $ do
it "zero" $ do
(decode "0") `shouldBe` (Right $ Number 0)
Expand All @@ -65,3 +64,53 @@ main = hspec $ do
(decode "3.6589e3") `shouldBe` (Right $ Number 3658.9)
it "scienticfic 2" $ do
(decode "3.6589e-3") `shouldBe` (Right $ Number 0.0036589)

describe "Rison Writer" $ do
context "rison tokens" $ do
it "null" $ do
(encode Null) `shouldBe` "!n"
it "true" $ do
(encode (Bool True)) `shouldBe` "!t"
it "false" $ do
(encode (Bool False)) `shouldBe` "!f"

context "strings" $ do
it "simple string" $ do
encode (String "ahoj") `shouldBe` "ahoj"
it "escaped values" $ do
encode (String "!\\") `shouldBe` "'!!!\\'"

context "numbers" $ do
it "zero" $ do
encode (Number 0) `shouldBe` "0"
it "simple integer" $ do
encode (Number 36589) `shouldBe` "36589"
it "simple decimal" $ do
encode (Number 36589.65200004) `shouldBe` "36589.65200004"

context "object" $ do
it "empty" $ do
encode (Object H.empty) `shouldBe` "()"
it "simple" $ do
encode (Object $ H.fromList [("property", Null)]) `shouldBe`
"(property:!n)"
it "simple with unquoted string" $ do
encode (Object $ H.fromList [("property", String "Off")]) `shouldBe`
"(property:Off)"
it "sorted properties" $ do
encode (Object $ H.fromList [ ("d", String "d")
, ("b", String "b")
, ("g", String "g")
, ("a", String "a")]) `shouldBe`
"(a:a,b:b,d:d,g:g)"

context "array" $ do
it "empty" $ do
encode (Array V.empty) `shouldBe` "!()"
it "booleans" $ do
encode (Array $ V.fromList [ Bool True
, Bool True
, Bool False
, Bool True
, Bool False]) `shouldBe`
"!(!t,!t,!f,!t,!f)"

0 comments on commit 02fde77

Please sign in to comment.