packages feed

axel-0.0.11: resources/autogenerated/macros/AST.hs

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeFamilies #-}

-- NOTE Because this file will be used as the header of auto-generated macro programs,
--      it can't have any project-specific dependencies (such as `Fix`).
module Axel.Parse.AST where

import Data.IORef (IORef, modifyIORef, newIORef, readIORef)
import Data.Semigroup ((<>))

import System.IO.Unsafe (unsafePerformIO)

handleStringEscapes :: String -> String
handleStringEscapes =
  concatMap $ \case
    '\\' -> "\\\\"
    c -> [c]

-- ******************************
-- ** AST                      **
-- ******************************
-- TODO `Expression` should probably be `Traversable`, use recursion schemes, etc.
--      We should provide `toFix` and `fromFix` functions for macros to take advantage of.
--      (Maybe all macros have the argument automatically `fromFix`-ed to make consumption simpler?)
data Expression ann
  = LiteralChar ann Char
  | LiteralInt ann Int
  | LiteralString ann String
  | SExpression ann [Expression ann]
  | Symbol ann String
  deriving (Eq, Functor, Show)

bottomUpFmap ::
     (Expression ann -> Expression ann) -> Expression ann -> Expression ann
bottomUpFmap f x =
  f $
  case x of
    LiteralChar _ _ -> x
    LiteralInt _ _ -> x
    LiteralString _ _ -> x
    SExpression ann' xs -> SExpression ann' (map (bottomUpFmap f) xs)
    Symbol _ _ -> x

bottomUpTraverse ::
     (Monad m)
  => (Expression ann -> m (Expression ann))
  -> Expression ann
  -> m (Expression ann)
bottomUpTraverse f x =
  f =<<
  case x of
    LiteralChar _ _ -> pure x
    LiteralInt _ _ -> pure x
    LiteralString _ _ -> pure x
    SExpression ann' xs -> SExpression ann' <$> traverse (bottomUpTraverse f) xs
    Symbol _ _ -> pure x

topDownFmap ::
     (Expression ann -> Expression ann) -> Expression ann -> Expression ann
topDownFmap f x =
  case f x of
    LiteralChar _ _ -> x
    LiteralInt _ _ -> x
    LiteralString _ _ -> x
    SExpression ann' xs -> SExpression ann' (map (topDownFmap f) xs)
    Symbol _ _ -> x

-- ******************************
-- ** Sourcemap                **
-- ******************************
data SourcePosition =
  SourcePosition
    { line :: Int
    , column :: Int
    }
  deriving (Eq, Show)

renderSourcePosition :: SourcePosition -> String
renderSourcePosition sourcePosition =
  show (line sourcePosition) <> ":" <> show (column sourcePosition)

data SourceMetadata
  = Nothing
  | FromSource SourcePosition
  deriving (Eq, Show)

-- ******************************
-- ** Internal                 **
-- ******************************
toAxel :: Expression ann -> String
toAxel (LiteralChar _ x) = ['{', x, '}']
toAxel (LiteralInt _ x) = show x
toAxel (LiteralString _ xs) = "\"" <> xs <> "\""
toAxel (SExpression _ (Symbol _ "applyInfix":xs)) =
  "{" <> unwords (map toAxel xs) <> "}"
toAxel (SExpression _ (Symbol _ "list":xs)) =
  "[" <> unwords (map toAxel xs) <> "]"
toAxel (SExpression _ [Symbol _ "quote", x]) = '\'' : toAxel x
toAxel (SExpression _ [Symbol _ "quasiquote", x]) = '`' : toAxel x
toAxel (SExpression _ [Symbol _ "unquote", x]) = '~' : toAxel x
toAxel (SExpression _ [Symbol _ "unquoteSplicing", x]) = "~@" <> toAxel x
toAxel (SExpression _ xs) = "(" <> unwords (map toAxel xs) <> ")"
toAxel (Symbol _ x) = x

-- ******************************
-- ** Quoting                  **
-- ******************************
-- TODO Derive these with Template Haskell (they're currently very brittle)
quoteSourceMetadata :: SourceMetadata -> Expression SourceMetadata
quoteSourceMetadata sm@Nothing = Symbol sm "AST.Nothing"
quoteSourceMetadata sm@(FromSource sourcePosition) =
  SExpression
    sm
    [ Symbol sm "AST.FromSource"
    , SExpression
        sm
        [ Symbol sm "AST.SourcePosition"
        , LiteralInt sm (line sourcePosition)
        , LiteralInt sm (column sourcePosition)
        ]
    ]

quoteParseExpression :: Expression SourceMetadata -> Expression SourceMetadata
quoteParseExpression (LiteralChar ann' x) =
  SExpression
    ann'
    [ Symbol ann' "AST.LiteralChar"
    , quoteSourceMetadata ann'
    , LiteralChar ann' x
    ]
quoteParseExpression (LiteralInt ann' x) =
  SExpression
    ann'
    [Symbol ann' "AST.LiteralInt", quoteSourceMetadata ann', LiteralInt ann' x]
quoteParseExpression (LiteralString ann' x) =
  SExpression
    ann'
    [ Symbol ann' "AST.LiteralString"
    , quoteSourceMetadata ann'
    , LiteralString ann' x
    ]
quoteParseExpression (SExpression ann' xs) =
  SExpression
    ann'
    [ Symbol ann' "AST.SExpression"
    , quoteSourceMetadata ann'
    , SExpression ann' (Symbol ann' "list" : map quoteParseExpression xs)
    ]
quoteParseExpression (Symbol ann' x) =
  SExpression
    ann'
    [ Symbol ann' "AST.Symbol"
    , quoteSourceMetadata ann'
    , LiteralString ann' (handleStringEscapes x)
    ]

-- ******************************
-- ** Macro definition         **
-- ******************************
{-# NOINLINE gensymCounter #-}
gensymCounter :: IORef Int
gensymCounter = unsafePerformIO $ newIORef 0

gensym :: IO (Expression SourceMetadata)
gensym = do
  suffix <- readIORef gensymCounter
  let identifier = "aXEL_AUTOGENERATED_IDENTIFIER_" <> show suffix
  modifyIORef gensymCounter succ
  pure $ Symbol Nothing identifier

-- | This allows splice-unquoting of both `[Expression]`s and `SExpression`s,
--   without requiring special syntax for each.
class ToExpressionList a where
  type Annotation a
  toExpressionList :: a -> [Expression (Annotation a)]

instance ToExpressionList [Expression ann] where
  type Annotation [Expression ann] = ann
  toExpressionList :: [Expression ann] -> [Expression ann]
  toExpressionList = id

-- | Because we do not have a way to statically ensure an `SExpression` is passed
--   (and not another one of `Expression`'s constructors instead),
--   we will error at compile-time if a macro attempts to splice-unquote inappropriately.
instance ToExpressionList (Expression ann) where
  type Annotation (Expression ann) = ann
  toExpressionList :: Expression ann -> [Expression ann]
  toExpressionList (SExpression _ xs) = xs
  toExpressionList x =
    error
      (toAxel x <>
       " cannot be splice-unquoted, because it is not an s-expression!")

wrapCompoundExpressions ::
     [Expression SourceMetadata] -> Expression SourceMetadata
wrapCompoundExpressions stmts =
  SExpression Nothing (Symbol Nothing "begin" : stmts)

unwrapCompoundExpressions :: Expression ann -> [Expression ann]
unwrapCompoundExpressions (SExpression _ (Symbol _ "begin":stmts)) = stmts
unwrapCompoundExpressions _ =
  error "unwrapCompoundExpressions must be passed a top-level program!"