{-# LANGUAGE DeriveDataTypeable   #-}
{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE RankNTypes           #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

module Expr where

import Prelude hiding ((.), id)
import Control.Category
import Data.Data (Data)
import qualified Data.ByteString.Lazy.Char8 as B8
import Data.Text (Text)

import qualified Language.Sexp.Located as Sexp
import Language.SexpGrammar
import Language.SexpGrammar.Generic
import GHC.Generics

newtype Ident = Ident Text
  deriving (Show, Generic)

data Expr
  = Var Ident
  | Lit Int
  | Add Expr Expr
  | Mul Expr Expr
  | Neg Expr
  | Inv Expr
  | IfZero Expr Expr (Maybe Expr)
  | Apply [Expr] String Prim -- inconvenient ordering: arguments, useless annotation, identifier
    deriving (Show, Generic)

data Prim
  = SquareRoot
  | Factorial
  | Fibonacci
    deriving (Eq, Enum, Bounded, Data, Show, Generic)

instance SexpIso Prim where
  sexpIso = match
    $ With (sym "square-root" >>>)
    $ With (sym "factorial" >>>)
    $ With (sym "fibonacci" >>>)
    $ End

instance SexpIso Ident where
  sexpIso = with (\ident -> ident . symbol)

instance SexpIso Expr where
  sexpIso = match
    $ With (\var -> var . sexpIso)
    $ With (\lit -> lit . int)
    $ With (\add -> add . list (el (sym "+") >>> el sexpIso >>> el sexpIso))
    $ With (\mul -> mul . list (el (sym "*") >>> el sexpIso >>> el sexpIso))
    $ With (\neg -> neg . list (el (sym "negate") >>> el sexpIso))
    $ With (\inv -> inv . list (el (sym "invert") >>> el sexpIso))
    $ With (\ifz -> ifz . list (el (sym "cond") >>> props ( "pred"  .: sexpIso
                                                        >>> "true"  .:  sexpIso
                                                        >>> "false" .:? sexpIso )))
    $ With (\app -> app . list
        (el (sexpIso :: SexpGrammar Prim) >>>   -- Push prim:       prim :- ()
         el (kwd "args") >>>                    -- Recognize :args, push nothing
         rest (sexpIso :: SexpGrammar Expr) >>> -- Push args:       args :- prim :- ()
         onTail (swap >>> push "dummy"
                  (const True)
                  (const (expected "dummy")) >>> swap)
        ))
    $ End

exprGrammar :: SexpGrammar Expr
exprGrammar = sexpIso

test :: String -> SexpGrammar a -> (a, String)
test str g = either error id $ do
  e <- decodeWith g "<stdio>" (B8.pack str)
  sexp' <- toSexp g e
  return (e, B8.unpack (Sexp.format sexp'))

-- > test "(cond :pred 1 :true (+ 42 10) :false (* 2 (* 2 2)))"
-- (IfZero (Lit 1) (Add (Lit 42) (Lit 10)) (Mul (Lit 2) (Mul (Lit 2) (Lit 2))),"(cond 1 (+ 42 10) (* 2 (* 2 2)))")
