content: {-# LANGUAGE OverloadedStrings #-}
  
  module Main where
  
  import Control.Applicative ((<|>))
  import Data.Char (isDigit)
  import Data.SCargot
  import Data.SCargot.Repr.Basic
  import Data.Text (Text, pack)
  import Numeric (readHex)
  import System.Environment (getArgs)
  import Text.Parsec (anyChar, char, digit, many1, manyTill, newline, satisfy, string)
  import Text.Parsec.Text (Parser)
  
  -- Our operators are going to represent addition, subtraction, or
  -- multiplication
  data Op = Add | Sub | Mul deriving (Eq, Show)
  
  -- The atoms of our language are either one of the aforementioned
  -- operators, or positive integers
  data Atom = AOp Op | ANum Int deriving (Eq, Show)
  
  -- Once parsed, our language will consist of the applications of
  -- binary operators with literal integers at the leaves
  data Expr = EOp Op Expr Expr | ENum Int deriving (Eq, Show)
  
  -- Conversions to and from our Expr type
  toExpr :: SExpr Atom -> Either String Expr
  toExpr (A (AOp op) ::: l ::: r ::: Nil) = EOp op <$> toExpr l <*> toExpr r
  toExpr (A (ANum n)) = pure (ENum n)
  toExpr sexpr = Left ("Unable to parse expression: " ++ show sexpr)
  
  fromExpr :: Expr -> SExpr Atom
  fromExpr (EOp op l r) = A (AOp op) ::: fromExpr l ::: fromExpr r ::: Nil
  fromExpr (ENum n) = A (ANum n) ::: Nil
  
  -- Parser and serializer for our Atom type
  pAtom :: Parser Atom
  pAtom =
    ((ANum . read) <$> many1 digit)
      <|> (char '+' *> pure (AOp Add))
      <|> (char '-' *> pure (AOp Sub))
      <|> (char '*' *> pure (AOp Mul))
  
  sAtom :: Atom -> Text
  sAtom (AOp Add) = "+"
  sAtom (AOp Sub) = "-"
  sAtom (AOp Mul) = "*"
  sAtom (ANum n) = pack (show n)
  
  -- Our comment syntax is going to be Haskell-like:
  hsComment :: Parser ()
  hsComment = string "--" >> manyTill anyChar newline >> return ()
  
  -- Our custom reader macro: grab the parse stream and read a
  -- hexadecimal number from it:
  hexReader :: Reader Atom
  hexReader _ = (A . ANum . rd) <$> many1 (satisfy isHexDigit)
    where
      isHexDigit c = isDigit c || c `elem` hexChars
      rd = fst . head . readHex
      hexChars :: String
      hexChars = "AaBbCcDdEeFf"
  
  -- Our final s-expression parser and printer:
  myLangParser :: SExprParser Atom Expr
  myLangParser =
    setComment hsComment $ -- set comment syntax to be Haskell-style
      addReader '#' hexReader $ -- add hex reader
        setCarrier toExpr $ -- convert final repr to Expr
          mkParser pAtom -- create spec with Atom type
  
  mkLangPrinter :: SExprPrinter Atom Expr
  mkLangPrinter =
    setFromCarrier fromExpr $
      setIndentStrategy (const Align) $
        basicPrint sAtom
  
  main :: IO ()
  main = do
    sExprText <- pack <$> getContents
    either putStrLn print (decode myLangParser sExprText)
  
  {-
  Example usage:
  
  $ dist/build/example/example <<EOF
  > -- you can put comments in the code!
  > (+ 10 (* 20 20))
  > -- and more than one s-expression!
  > (* 10 10)
  > EOF
  [EOp Add (ENum 10) (EOp Mul (ENum 20) (ENum 20)),EOp Mul (ENum 10) (ENum 10)]
  -}
  