rubyhs/src/Data/Language/Ruby/AST.hs

360 lines
9.8 KiB
Haskell
Raw Normal View History

2019-10-16 18:14:48 +00:00
{-# LANGUAGE DuplicateRecordFields, OverloadedLists #-}
{-# OPTIONS_GHC -Wall #-}
module Data.Language.Ruby.AST
( Args(..)
2019-10-17 18:08:25 +00:00
, Begin(..)
2019-10-16 18:14:48 +00:00
, Statement(..)
, Function(..)
, Module(..)
, Name(..)
, Send(..)
, Namespace(..)
2019-10-17 18:08:25 +00:00
, Block(..)
2019-10-16 20:12:30 +00:00
, Casgn(..)
, RArray(..)
, RArgs(..)
2019-10-16 20:12:30 +00:00
, Anything(..)
2019-10-17 18:08:25 +00:00
, Sym(..)
2019-10-16 18:14:48 +00:00
) where
import Data.Aeson (parseJSON, Value(..), withArray)
import Frelude
import qualified Data.Aeson.Types as Aeson
import qualified Data.Vector as Vector
import Data.Coerce
import Data.Word
kebabCase :: String -> String
kebabCase = Aeson.camelTo2 '-'
aesonOptions :: Aeson.Options
aesonOptions = Aeson.defaultOptions
{ Aeson.sumEncoding = Aeson.ObjectWithSingleField
, Aeson.constructorTagModifier = kebabCase
}
2019-10-17 18:08:25 +00:00
newtype Begin = Begin [Statement]
2019-10-16 18:14:48 +00:00
2019-10-17 18:08:25 +00:00
deriving stock instance Show Begin
deriving stock instance Ord Begin
deriving stock instance Eq Begin
deriving stock instance Generic Begin
instance ToJSON Begin where
2019-10-16 18:14:48 +00:00
toEncoding = Aeson.genericToEncoding aesonOptions
2019-10-17 18:08:25 +00:00
deriving newtype instance Semigroup Begin
deriving newtype instance Monoid Begin
2019-10-16 18:14:48 +00:00
2019-10-17 18:08:25 +00:00
instance FromJSON Begin where
parseJSON = withArray "Begin" $ \as -> case Vector.toList as of
(String "begin":xs) -> Begin <$> traverse parseJSON xs
_ -> Begin . pure <$> parseJSON (Array as)
2019-10-16 18:14:48 +00:00
-- Should be 'expression'
data Statement
= StmtModule Module
| StmtFunction Function
| StmtSend Send
2019-10-17 18:08:25 +00:00
| StmtBlock Block
2019-10-16 18:14:48 +00:00
| StmtConst Namespace
2019-10-16 20:12:30 +00:00
| StmtCasgn Casgn
| StmtArray RArray
2019-10-17 18:08:25 +00:00
| StmtSym Sym
2019-10-16 20:12:30 +00:00
-- TODO Get rid of this
2019-10-16 18:14:48 +00:00
| StmtAnything Anything
deriving stock instance Show Statement
deriving stock instance Ord Statement
deriving stock instance Eq Statement
deriving stock instance Generic Statement
instance ToJSON Statement where
toEncoding = Aeson.genericToEncoding opts
where
opts = aesonOptions { Aeson.constructorTagModifier = go }
go = \case
"StmtModule" -> "module"
"StmtFunction" -> "function"
"StmtSend" -> "send"
2019-10-17 18:08:25 +00:00
"StmtBlock" -> "block"
2019-10-16 18:14:48 +00:00
"StmtConst" -> "const"
2019-10-17 18:08:25 +00:00
"StmtCasgn" -> "casgn"
"StmtArray" -> "array"
"StmtSym" -> "sym"
x -> x
2019-10-16 18:14:48 +00:00
instance FromJSON Statement where
parseJSON v
= (StmtModule <$> parseJSON v)
<|> (StmtFunction <$> parseJSON v)
<|> (StmtSend <$> parseJSON v)
2019-10-17 18:08:25 +00:00
<|> (StmtBlock <$> parseJSON v)
2019-10-16 18:14:48 +00:00
<|> (StmtConst <$> parseJSON v)
2019-10-16 20:12:30 +00:00
<|> (StmtCasgn <$> parseJSON v)
<|> (StmtArray <$> parseJSON v)
2019-10-16 18:14:48 +00:00
<|> (StmtAnything <$> parseJSON v)
2019-10-16 20:12:30 +00:00
data Casgn = Casgn
{ name :: Name
, statement :: Statement
}
deriving stock instance Show Casgn
deriving stock instance Ord Casgn
deriving stock instance Eq Casgn
deriving stock instance Generic Casgn
instance ToJSON Casgn where
toEncoding = Aeson.genericToEncoding aesonOptions
instance FromJSON Casgn where
parseJSON = withArray "Module" $ \case
[String "casgn", _, name, statement]
-> Casgn
<$> parseJSON name
<*> parseJSON statement
_ -> empty
data RArray = RArray
{ statements :: [Statement]
}
deriving stock instance Show RArray
deriving stock instance Ord RArray
deriving stock instance Eq RArray
deriving stock instance Generic RArray
instance ToJSON RArray where
toEncoding = Aeson.genericToEncoding aesonOptions
instance FromJSON RArray where
parseJSON = withArray "RArray" $ \as -> case Vector.toList as of
String "array":xs
-> RArray
<$> traverse parseJSON xs
_ -> empty
2019-10-16 18:14:48 +00:00
newtype Anything = Anything Value
deriving stock instance Show Anything
instance Ord Anything where
compare = coerce compareValue
deriving stock instance Eq Anything
deriving stock instance Generic Anything
instance ToJSON Anything where
toEncoding = Aeson.genericToEncoding aesonOptions
deriving newtype instance FromJSON Anything
-- f do |x|
-- expr
-- end
2019-10-17 18:08:25 +00:00
data Block = Block
2019-10-16 18:14:48 +00:00
{ send :: Send
, args :: RArgs
2019-10-17 18:08:25 +00:00
, begin :: Begin
2019-10-16 18:14:48 +00:00
}
2019-10-17 18:08:25 +00:00
deriving stock instance Show Block
deriving stock instance Ord Block
deriving stock instance Eq Block
deriving stock instance Generic Block
instance ToJSON Block where
2019-10-16 18:14:48 +00:00
toEncoding = Aeson.genericToEncoding aesonOptions
2019-10-17 18:08:25 +00:00
instance FromJSON Block where
parseJSON = withArray "Block" $ \as -> case Vector.toList as of
(String "block":send:args:begin:[])
-> Block
2019-10-16 18:14:48 +00:00
<$> parseJSON send
<*> parseJSON args
2019-10-17 18:08:25 +00:00
<*> parseJSON begin
2019-10-16 18:14:48 +00:00
_ -> empty
-- | It's super confusing that I've already defined a node in my AST
-- called args. This one correspond to the AST node with the label
-- "args" as reported by `ruby-parse`.
2019-10-17 18:08:25 +00:00
newtype RArgs = RArgs [Arg]
deriving stock instance Show RArgs
deriving stock instance Ord RArgs
deriving stock instance Eq RArgs
deriving stock instance Generic RArgs
instance ToJSON RArgs where
toEncoding = Aeson.genericToEncoding aesonOptions
2019-10-17 18:08:25 +00:00
instance FromJSON RArgs where
parseJSON = withArray "RArgs" $ \as -> case Vector.toList as of
(String "args":xs) -> RArgs <$> traverse parseJSON xs
_ -> empty
data Arg = Arg Atom | KWArg Atom
deriving stock instance Show Arg
deriving stock instance Ord Arg
deriving stock instance Eq Arg
deriving stock instance Generic Arg
instance ToJSON Arg where
toEncoding = Aeson.genericToEncoding opts
where
opts = aesonOptions { Aeson.constructorTagModifier = go }
go = \case
"KWArg" -> "kwarg"
"Arg" -> "arg"
x -> x
instance FromJSON Arg where
parseJSON = withArray "Arg" $ \as -> case Vector.toList as of
[String "arg" , symbol] -> Arg <$> parseJSON symbol
[String "kwarg" , symbol] -> KWArg <$> parseJSON symbol
_ -> empty
newtype Atom = Atom Text
deriving stock instance Show Atom
deriving stock instance Ord Atom
deriving stock instance Eq Atom
deriving stock instance Generic Atom
instance ToJSON Atom where
toEncoding = Aeson.genericToEncoding aesonOptions
instance FromJSON Atom where
parseJSON = \case
String s -> pure $ Atom s
_ -> empty
2019-10-16 18:14:48 +00:00
newtype Namespace = Namespace [Name]
deriving newtype instance Semigroup Namespace
deriving newtype instance Monoid Namespace
deriving stock instance Show Namespace
deriving stock instance Ord Namespace
deriving stock instance Eq Namespace
deriving stock instance Generic Namespace
instance ToJSON Namespace where
toEncoding = Aeson.genericToEncoding aesonOptions
instance FromJSON Namespace where
parseJSON = \case
Null -> pure mempty
Array [String "const", namespace, String name] -> (<> Namespace [Name $ String name]) <$> parseJSON namespace
_ -> empty
data Send = Send
{ args :: Args
, namespace :: Namespace
, name :: Name
}
deriving stock instance Show Send
deriving stock instance Ord Send
deriving stock instance Eq Send
deriving stock instance Generic Send
instance ToJSON Send where
toEncoding = Aeson.genericToEncoding aesonOptions
instance FromJSON Send where
parseJSON = withArray "Send" $ \ as -> case Vector.toList as of
(String "send" : namespace : name : args)
-> Send
<$> parseJSON (Array $ Vector.fromList args)
<*> parseJSON namespace
<*> parseJSON name
_ -> empty
data Module = Module
2019-10-17 18:08:25 +00:00
{ name :: Name
, begin :: Begin
2019-10-16 18:14:48 +00:00
}
deriving stock instance Show Module
deriving stock instance Ord Module
deriving stock instance Eq Module
deriving stock instance Generic Module
instance ToJSON Module where
toEncoding = Aeson.genericToEncoding aesonOptions
instance FromJSON Module where
parseJSON = withArray "Module" $ \case
2019-10-17 18:08:25 +00:00
[String "module", name, begin]
2019-10-16 18:14:48 +00:00
-> Module
<$> parseJSON name
2019-10-17 18:08:25 +00:00
<*> parseMaybe begin
[String "class", name, _, begin]
2019-10-16 18:14:48 +00:00
-> Module
<$> parseJSON name
2019-10-17 18:08:25 +00:00
<*> parseMaybe begin
2019-10-16 18:14:48 +00:00
_ -> empty
parseMaybe :: FromJSON m => Monoid m => Value -> Aeson.Parser m
parseMaybe = \case
Null -> pure mempty
x -> parseJSON x
data Function = Function
2019-10-17 18:08:25 +00:00
{ name :: Name
, args :: RArgs
, begin :: Begin
2019-10-16 18:14:48 +00:00
}
deriving stock instance Show Function
deriving stock instance Ord Function
deriving stock instance Eq Function
deriving stock instance Generic Function
instance ToJSON Function where
toEncoding = Aeson.genericToEncoding aesonOptions
data Args = Args [Statement]
2019-10-16 18:14:48 +00:00
deriving stock instance Show Args
deriving stock instance Ord Args
2019-10-16 18:14:48 +00:00
deriving stock instance Eq Args
deriving stock instance Generic Args
instance ToJSON Args where
toEncoding = Aeson.genericToEncoding aesonOptions
instance FromJSON Args where
parseJSON = withArray "Args" $ \xs -> Args <$> traverse parseJSON (toList xs)
2019-10-16 18:14:48 +00:00
instance FromJSON Function where
parseJSON = withArray "Function" $ \case
2019-10-17 18:08:25 +00:00
[String "def", name, args, begin]
2019-10-16 18:14:48 +00:00
-> Function
<$> parseJSON name
<*> parseJSON args
2019-10-17 18:08:25 +00:00
<*> parseMaybe begin
2019-10-16 18:14:48 +00:00
_ -> empty
newtype Name = Name Value
deriving stock instance Show Name
instance Ord Name where
compare = coerce compareValue
deriving stock instance Eq Name
deriving newtype instance ToJSON Name
deriving newtype instance FromJSON Name
compareValue :: Aeson.Value -> Aeson.Value -> Ordering
compareValue v0 v1 = case (v0, v1) of
-- This case is buggy:
(Object o0, Object o1) -> compare (fst <$> toList o0) (fst <$> toList o1)
(Array a0, Array a1) -> foldMap (uncurry compareValue) $ zip (toList a0) (toList a1)
(String s0, String s1) -> compare s0 s1
(Number n0, Number n1) -> compare n0 n1
(Bool b0, Bool b1) -> compare b0 b1
(Null, Null) -> EQ
_ -> (compare `on` cons) v0 v1
where
-- Enumerate constructors.
cons :: Aeson.Value -> Word8
cons = \case
Object{} -> 0
Array{} -> 1
String{} -> 2
Number{} -> 3
Bool{} -> 4
Null{} -> 5
2019-10-17 18:08:25 +00:00
newtype Sym = Sym Atom
deriving stock instance Show Sym
deriving newtype instance Ord Sym
deriving stock instance Eq Sym
deriving stock instance Generic Sym
instance ToJSON Sym where
toEncoding = Aeson.genericToEncoding aesonOptions
instance FromJSON Sym where
parseJSON = withArray "Sym" $ \case
[String "sym", atom] -> Sym <$> parseJSON atom
_ -> empty