packages feed

x-dsp-0.2: tools/mkOp.hs

module Main (main) where

import           CsMaps
import           CsParser

import           Sound.Csound
import           Control.Arrow ((***), first)
import           Control.Applicative ((*>), (<*))
import           Control.Monad.Error
import           Control.Monad.State
import           Control.Monad.Writer
import qualified Data.Map as M
import qualified Data.Set as S
import           Data.Maybe
import           Text.Printf
import           Data.Char
import           Data.Either
import           Data.List (intercalate)

foldM1 f [] = error "foldM1 called on empty list"
foldM1 f (x:xs) = foldM f x xs

fromRight (Right val) = val
fromRight n           = error $ "fromRight called on Left" ++ show n

-- scheme for handling instances:
-- group all opcodes by name and determine var positions
-- group opcodes by output (a,k,i, other)
-- merge each group, separate instance for each output type
-- k-inputs can be k- or i-, use the KVar class

-- | partition opcode list into a list of
--  (name, fully-merged opcode, opcodes merged by outarg)
partitionOutarg :: [OpcodeListEntry]
  -> ([String], [(String, OpcodeListEntry, [(OType,OpcodeListEntry)])])
partitionOutarg = partitionEithers . map mungeTup . M.assocs . foldr f M.empty
 where
  -- make map of (name) to list of opcodes
  f ocl = M.insertWith (++) (opcodeName ocl) [ocl]
  g ocl = M.insertWith (++) (either (const SS) id . parseOutarg $ ouTypes ocl) [ocl]
  mungeTup (nm, ocls) = do
    fullMerge <- foldM1 mixOcl ocls
    let (chk,oMap) = M.mapEither (foldM1 mixOcl) $ foldr g M.empty ocls
    when (not $ M.null chk) (error "chk map isn't empty, shouldn't happen")
    return (nm, fullMerge, M.assocs oMap)

mixOcl :: OpcodeListEntry -> OpcodeListEntry -> Either String OpcodeListEntry
mixOcl a b | opcodeName a == opcodeName b = do
  los <- zipWithM mixLO (ouTypes a) (ouTypes b)
  return $ OpcodeListEntry (opcodeName a) los (zipWith mixL (inTypes a) (inTypes b))
mixOcl a b = throwError "mixOcl called with non-matching entries"

type SpCtx = String -- stub

cap :: String -> String
cap []     = []
cap (a:as) = toUpper a:as

unCap :: String -> String
unCap []     = []
unCap (a:as) = toLower a:as


-- ops' :: (Name, MergedOp, [(OType, MergedOp)])
-- for each MergedOp, get the tyvar positions
-- then write an instance for each OType, filling in the tyvars

mkInstance :: String -> OpcodeListEntry -> (OType, OpcodeListEntry)
  -> Writer [String] String
mkInstance _ flmrg (otype, ole) = 
 case (getInArgTyVars (inTypes flmrg), parseOutarg (ouTypes flmrg)) of
   (Right i, Right o) -> return $ topline i o ++ mainline i o ++ oline i o
   (Left ie, Left oe) -> tell [opc,ie,oe] >> return ""
   (Left ie, _)       -> tell [opc,ie] >> return ""
   (_,       Left oe) -> tell [opc,oe] >> return ""
 where
  opc = let n' = unCap $ opcodeName ole
        in fromMaybe n' $ M.lookup n' invalidNameMap
  lookupInTyp (pos,vnm) = either
                          (\nm' -> Left $ nm' ++ [vnm])
                          (Right)
                          (inArgInstanceInfo $ (inTypes ole) !! pos)
  tyVars (pos,vnm) = either (const [vnm])
                            id
                            (inArgInstanceInfo $ (inTypes ole) !! pos)
  -- inargs :: [(VarPos, VarName)]
  topline (tnum,inargs,reqNum) fmOt =
    printf "instance %s %s (S n) %s where\n"
           (if null allHd then ""
             else printf "(%s) =>" (intercalate ", " allHd))
           (qualifyName $ "Cs" ++ cap opc)
           allDecl
     where
       (inHd, _) = partitionEithers $ map lookupInTyp inargs
       -- if the instance outtype needs a context, provide it
       allHd = maybe inHd ((:inHd) . fst) (instrCtx otype)
       inDecls = map tyVars inargs
       allDecl = unwords $ if oHasCtxt fmOt then (monoCtxt otype:inDecls) else inDecls
  mainline (tnum,inargs,reqNum) _ =
    printf "  %s = mkOp%d \"%s\"\n" opc (fromMaybe tnum reqNum) opc
  oline (tnum,inargs,reqNum) _ =
    maybe "" (const $ printf "  %s' = mkOp%d \"%s\"\n" opc tnum opc) reqNum

allOps = do
  ops <- runCsound (compile "-d foo.csd" >> newOpcodeList <* cleanup)
  return $ either (error . show) id ops

writeOut fn ctx ops = do
  writeFile fn $ intercalate "\n\n" . filter (not . null) $ concat vals
  writeFile ("err_" ++ fn) $ intercalate "\n\n" (errs ++ errs1)
 where
  -- ops' :: [ (Name, MergedOp, [(OType, MergedOp)]) ]
  (errs1, ops') = partitionOutarg $ filter (not . flip S.member noReuseSet . opcodeName) ops
  (vals,errs) = runWriter $ mapM (\(a,b,c) -> mapM (mkInstance a b) c) ops'

main = allOps >>= writeOut "csdInstances.txt" "CsoundClass repr =>"