packages feed

cpsa-2.3.4: src/CPSA/Lib/Expand.hs

-- Expands macros using definitions in the input.

-- Copyright (c) 2009 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.

module CPSA.Lib.Expand (expand, readSExprs) where

import Control.Monad
import System.IO (openFile, IOMode (ReadMode))
import CPSA.Lib.Entry (readSExpr, tryIO)
import CPSA.Lib.CPSA

-- The macroexpand loop limit
limit :: Int
limit = 1000

-- Include bound
bound :: Int
bound = 16

expand :: [SExpr Pos] -> IO [SExpr Pos]
expand sexprs =
    do
      (_, sexprs) <- foldM (expandSExpr bound) ([], []) sexprs
      return (reverse sexprs)

expandSExpr :: Int -> ([Macro], [SExpr Pos]) -> SExpr Pos ->
               IO ([Macro], [SExpr Pos])
expandSExpr _ (macs, sexprs) (L pos (S _ "defmacro" : xs)) =
    do                          -- Process a macro definition
      mac <- defmacro pos xs
      return (mac : macs, sexprs)
expandSExpr bound (macs, sexprs) (L pos [S _ "include", Q _ file]) =
    if bound <= 0 then
      fail (shows pos ("Include depth exceeded with file " ++ file))
    else
      include (bound - 1) (macs, sexprs) pos file
expandSExpr _ (macs, sexprs) sexpr =
    do                          -- Process all other S-expressions
      sexpr <- expandAll macs sexpr
      return (macs, sexpr : sexprs)

-- The include form is processed here.

include :: Int -> ([Macro], [SExpr Pos]) -> Pos -> String ->
           IO ([Macro], [SExpr Pos])
include bound (macs, sexprs) pos file =
    do
      input <- tryIO (openFile file ReadMode)
      case input of
        Left err -> fail (shows pos ("File " ++ file ++ ": " ++ err))
        Right input ->
          do
            p <- posHandle file input
            incsexprs <- readSExprs p
            foldM (expandSExpr bound) (macs, sexprs) incsexprs

readSExprs :: PosHandle -> IO [SExpr Pos]
readSExprs p =
    loop []
    where
      loop xs =
          do
            x <- readSExpr p
            case x of
              Nothing ->
                  return $ reverse xs
              Just x ->
                  loop (x:xs)

-- A macro definition is of the form:
--
-- (defmacro (NAME ARG*) BODY)
--
-- where NAME and each ARG is a symbol

data Macro = Macro
    { name :: String,
      args :: [String],
      body :: SExpr Pos }

defmacro :: Monad m => Pos -> [SExpr Pos] -> m Macro
defmacro _ [L _ (name : args), body] =
    do
      name <- symbol name
      args <- mapM symbol args
      return $ Macro { name = name,
                       args = args,
                       body = body}
defmacro pos _ = fail (shows pos "Malformed macro")

symbol :: Monad m => SExpr Pos -> m String
symbol (S _ string) = return string
symbol x = fail (shows (annotation x) "Expecting a symbol")

-- Expand an S-expression using a given set of macros

expandAll :: Monad m => [Macro] -> SExpr Pos -> m (SExpr Pos)
expandAll macs sexpr =
    do
      sexpr <- macroExpand macs (annotation sexpr) limit sexpr
      case sexpr of
        L pos xs ->             -- Expand elements of list
            do
              xs <- mapM (expandAll macs) xs
              return (L pos (splice xs))
        _ -> return sexpr

-- Expand one S-expression limiting the number of expansions.

macroExpand :: Monad m => [Macro] -> Pos ->  Int ->
               SExpr Pos -> m (SExpr Pos)
macroExpand _ pos limit _
    | limit <= 0 = fail (shows pos "Expansion limit exceded")
macroExpand macs pos limit sexpr@(L _ (S _ sym : xs)) =
    case macroExpand1 macs sym xs of
      Nothing -> return sexpr   -- Nothing to do
      Just sexpr -> macroExpand macs pos (limit - 1) sexpr
macroExpand _ _ _ sexpr = return sexpr

-- Expand one macro call or return Nothing

macroExpand1 :: [Macro] -> String -> [SExpr Pos] -> Maybe (SExpr Pos)
macroExpand1 [] _ _ = Nothing
macroExpand1 (mac : macs) sym xs
    | name mac == sym && length (args mac) == length xs =
        Just (apply mac xs)
    | otherwise =
        macroExpand1 macs sym xs

-- Apply a macro to some parameters

apply :: Macro -> [SExpr Pos] -> SExpr Pos
apply mac xs =
    subst (zip (args mac) xs) (body mac)

-- Substitute parameters into the macro body

subst :: [(String, SExpr Pos)] -> SExpr Pos -> SExpr Pos
subst env sexpr@(S _ sym) =
    maybe sexpr id (lookup sym env)
subst env (L pos sexprs) =
    L pos (map (subst env) sexprs)
subst _ sexpr = sexpr

-- Splice sexprs that start with splice symbol.  The splice symbol is ^.

splice :: [SExpr Pos] -> [SExpr Pos]
splice [] = []
splice ((L _ (S _ "^":xs)):ys) =
   loop xs ys
   where
     loop [] ys = splice ys
     loop (x:xs) ys = x:(loop xs ys)
splice (x:xs) = x:splice xs