packages feed

x-dsp-0.2: tools/CsParser.hs

module CsParser where

import           Control.Monad.Error
import           Control.Monad.State
import           Control.Monad.Writer
import           Data.List (intercalate)
import           Data.Maybe

-- http://www.perceptionfactory.com/csound/csound5extending.html
-- there's got to be a better way to encode this.  Maybe something like
-- a partial order?
mixL  a   b | a == b = a
mixL 'i' 'k' = 'k'
mixL 'i' 'a' = 'x'
mixL 'i' 'x' = 'x'
mixL 'i' 'S' = 'x'
mixL 'k' 'i' = 'k'
mixL 'k' 'a' = 'x'
mixL 'k' 'x' = 'x'
mixL 'k' 'S' = 'x'
mixL 'a' 'i' = 'x'
mixL 'a' 'k' = 'x'
mixL 'a' 'x' = 'x'
mixL 'a' 'S' = 'x'
mixL 'x' 'i' = 'x'
mixL 'x' 'k' = 'x'
mixL 'x' 'a' = 'x'
mixL 'S' 'i' = 'x'
mixL 'S' 'k' = 'x'
mixL 'S' 'a' = 'x'
mixL 'S' 'x' = 'x'
mixL _   _   = '*' -- don't have any means to handle other intypes yet.

-- | polyvar codes:
-- s : perf-time, a+k
-- c : control-time, k+i+S
-- v : any-time, a+k+i+S
mixLO :: Char -> Char -> Either String Char
mixLO 'v'  _  = return 'v'
mixLO  _  'v' = return 'v'
mixLO 's' 'i' = return 'v'
mixLO 's' 'c' = return 'v'
mixLO 's' 'S' = return 'v'
mixLO 's'  _  = return 's'
mixLO 'c' 's' = return 'v'
mixLO 'c' 'a' = return 'v'
mixLO 'c'  _  = return 'c' -- c,i,k,S
mixLO 'a' 'i' = return 'v'
mixLO 'a' 'k' = return 's'
mixLO 'a' 's' = return 's'
mixLO 'a' 'c' = return 'v'
mixLO 'a' 'S' = return 'v'
mixLO 'k' 'i' = return 'c'
mixLO 'k' 'a' = return 's'
mixLO 'k' 's' = return 's'
mixLO 'k' 'c' = return 'c'
mixLO 'k' 'S' = return 'c'
mixLO 'i' 'k' = return 'c'
mixLO 'i' 'a' = return 'v'
mixLO 'i' 'c' = return 'c'
mixLO 'i' 's' = return 'v'
mixLO 'i' 'S' = return 'c'
mixLO 'S' 'i' = return 'c'
mixLO 'S' 'k' = return 'c'
mixLO 'S' 'a' = return 'v'
mixLO 'S' 'c' = return 'c'
mixLO 'S' 's' = return 'v'
mixLO a b
  | a == b    = return a
  | otherwise = throwError $ "mixLO unexpected combination: " ++ [a,' ',b]

type ArgCtx r = StateT (Int,((Int,Int), Bool)) (Writer [String]) r

getLbl = do
  (x,b) <- get
  put (succ x,b)
  return (toEnum x)

incr = do
  (x,((i1,i2),b)) <- get
  if not b
    then put (x,((succ i1,i2),b))
    else put (x,((i1,succ i2),b))

setOpt = do
  (x,((i1,i2),b)) <- get
  if not b
    then put (x,((succ i1,i2),True))
    else put (x,((i1,succ i2),True))

tellS s = tell [s] >> return Nothing

optIRateInargs = "opqvjh"

parseInarg :: Char -> ArgCtx (Maybe String)
parseInarg 'a' = incr >> return (Just "ASig repr")
parseInarg 'k' = incr >> return (Just "KSig repr")
parseInarg 'i' = incr >> return (Just "INum repr")
parseInarg 'f' = incr >> return (Just "FSig repr")
parseInarg 'O' = setOpt >> getLbl >>= \l -> return $
  Just ([l]) -- optional i or k var
parseInarg 'p' = setOpt >> return (Just "INum repr")
parseInarg 'S' = incr >> return (Just "VString repr")
parseInarg 'T' = incr >> return (Just "VString repr") -- a file, which can either be a file name or a number (e.g. pvoc.nnn)
parseInarg 'x' = incr >> getLbl >>= \l -> return $
  Just [l] -- i or k var
parseInarg 'm' = incr >> return (Just "[INum repr]")
parseInarg 'M' = incr >> getLbl >>= \l -> return (Just $ "[" ++ [l] ++ "]")
parseInarg 'N' = incr >> return
  (Just "[(INum repr, INum repr)]")
parseInarg 'y' = incr >> return (Just "[ASig repr]")
parseInarg 'z' = incr >> return (Just "[KSig repr]")
parseInarg 'Z' = incr >> return (Just "[(KSig repr, ASig repr)]")
parseInarg c
  | c `elem` optIRateInargs = setOpt >> return (Just "INum repr")
parseInarg z   = tellS $ "unknown inarg: " ++ [z]

inargLbls :: Int -> Char -> ArgCtx (Maybe Int)
inargLbls i 'O' = parseInarg 'O' >> return (Just i)
inargLbls i 'x' = parseInarg 'x' >> return (Just i)
inargLbls i 'M' = parseInarg 'M' >> return (Just i)
inargLbls i c   = parseInarg c   >> return Nothing

-- | Left if in the instance head, Right if in the decl.
inArgInstanceInfo :: Char -> Either String String
inArgInstanceInfo 'a' = Right "(ASig (S n))"
inArgInstanceInfo 'k' = Right "(KSig (S n))"
inArgInstanceInfo 'i' = Right "(INum (S n))"
inArgInstanceInfo 'f' = Right "(FSig (S n))"
inArgInstanceInfo 'o' = Right "(INum (S n))"
inArgInstanceInfo 'O' = Left  "KVar "
inArgInstanceInfo 'p' = Right "(INum (S n))"
inArgInstanceInfo 'S' = Right "(VString (S n))"
inArgInstanceInfo 'T' = Right "(VString (S n))"
inArgInstanceInfo 'x' = Left  "Varable "
inArgInstanceInfo 'm' = Right "[INum (S n)]"
inArgInstanceInfo 'M' = Left  "Varable "
inArgInstanceInfo 'N' = Right "[(INum (S n), INum (S n))]"
inArgInstanceInfo 'y' = Right "[ASig (S n)]"
inArgInstanceInfo 'z' = Right "[KSig (S n)]"
inArgInstanceInfo 'Z' = Right "[(KSig (S n), ASig (S n))]"
inArgInstanceInfo _   = error "invalid inArg in 'inArgInstanceInfo'"

-- | modifications that change the number of inargs, must come before
--   other manipulations of inargs
preMungeInarg :: String -> String
preMungeInarg = concatMap f
 where
  f 'n' = "iN"
    -- convert an odd-numbered list to a required arg and even-numbered list
  f c   = [c]

-- | for an inarg, calc.
-- (totalNumArgs, [VarPos (0ix), VarName], Maybe reqNum)
getInArgTyVars :: String -> Either String (Int, [(Int,Char)], Maybe Int)
getInArgTyVars instrng' =
  either (Left)
         (\(tyvars,_,tnum,mreq) ->
             Right $ (tnum,zip (catMaybes mres) tyvars,mreq))
         (runParseI instrng)
 where
  instrng = preMungeInarg instrng'
  ((mres, _), _) =
    runWriter $ runStateT (mapM (uncurry inargLbls) $ zip [0..] instrng)
                          (fromEnum 'a', ((-1,-1), False))
  

runParseI :: String -> Either String ([Char], [String], Int, Maybe Int) -- (type variables, args, totalnum, maybe number of required args)
runParseI instrng' =
  maybe (Left $ intercalate "\n\t" (instrng:errS))
        (\resstrn -> Right
                     (['a' .. toEnum (lastLbl-1)]
                     ,resstrn
                     ,numReq+numOpt+2
                     ,if hasOpts then Just numReq else Nothing))
        (sequence mres)
 where
  instrng = preMungeInarg instrng'
  ((mres, (lastLbl,((numReq,numOpt), hasOpts))), errS) =
    runWriter $ runStateT (mapM parseInarg instrng)
                          (fromEnum 'a', ((-1,-1), False))


parseOutarg :: String -> Either String OType
parseOutarg ""  = Right U
parseOutarg "a" = Right A
parseOutarg "k" = Right K
parseOutarg "i" = Right I
parseOutarg "f" = Right F
parseOutarg "ik" = Right IK
parseOutarg "ki" = Right KI
parseOutarg "kk" = Right KK
parseOutarg "ff" = Right FF
parseOutarg "aa" = Right AA
parseOutarg "ak" = Right AK
parseOutarg "kkk"  = Right KKK
parseOutarg "aaa"  = Right AAA
parseOutarg "kkkk" = Right KKKK
parseOutarg "aaaa" = Right AAAA
parseOutarg "iiii" = Right IIII
parseOutarg "c" = Right C
parseOutarg "v" = Right V
parseOutarg "s" = Right S
parseOutarg "ss" = Right SS
parseOutarg "S" = Right Str
parseOutarg arg
 | arg == replicate (length arg) 'm' = Right MA
 | otherwise                         = Left $ "can't parse outarg: " ++ arg

data OType =
   A
 | K
 | I
 | F
 | U
 | IK
 | KI
 | KK
 | FF
 | AA
 | AK
 | AAA
 | KKK
 | AAAA
 | KKKK
 | IIII
 | C
 | V
 | S
 | SS
 | Str
 | MA
 deriving (Eq, Show, Read, Ord, Enum)

data OCtxt = OCtxt {
  var :: Bool
 ,nat :: Bool }
 deriving (Eq, Show)

hasOCtxt (OCtxt b1 b2) = b1 || b2

nullCtxt = OCtxt False False
outCtxt  = OCtxt True  False
natCtxt  = OCtxt False True

getCtxt (OCtxt False False) = ""
getCtxt (OCtxt True  False) = "out "
getCtxt (OCtxt False  True) = "d "
getCtxt (OCtxt True   True) = "out d "

-- | convert OType to a ctxtRepr (for mkClass)
ctxtRepr U  = (nullCtxt, "repr ()")
ctxtRepr A = (nullCtxt, "repr (ASig repr)")
ctxtRepr K = (nullCtxt, "repr (KSig repr)")
ctxtRepr I = (nullCtxt, "repr (INum repr)")
ctxtRepr F = (nullCtxt, "repr (FSig repr)")
ctxtRepr IK = (nullCtxt, "repr (INum repr, KSig repr)")
ctxtRepr KI = (nullCtxt, "repr (KSig repr, INum repr)")
ctxtRepr KK = (nullCtxt, "repr (KSig repr, KSig repr)")
ctxtRepr FF = (nullCtxt, "repr (FSig repr, FSig repr)")
ctxtRepr AA = (nullCtxt, "repr (ASig repr, ASig repr)")
ctxtRepr AK = (nullCtxt, "repr (ASig repr, KSig repr)")
ctxtRepr AAA = (nullCtxt, "repr (ASig repr, ASig repr, ASig repr)")
ctxtRepr KKK = (nullCtxt, "repr (KSig repr, KSig repr, KSig repr)")
ctxtRepr AAAA = (nullCtxt, "repr (ASig repr, ASig repr, ASig repr, ASig repr)")
ctxtRepr KKKK = (nullCtxt, "repr (KSig repr, KSig repr, KSig repr, KSig repr)")
ctxtRepr IIII = (nullCtxt, "repr (INum repr, INum repr, INum repr, INum repr)")
ctxtRepr MA   = (natCtxt, "repr (TList d (ASig repr))")
ctxtRepr S    = (outCtxt, "repr out")
ctxtRepr V    = (outCtxt, "repr out")
ctxtRepr C    = (outCtxt, "repr out")
ctxtRepr SS   = (outCtxt, "repr (out,out)")
ctxtRepr Str  = (nullCtxt, "repr (VString repr)")

iHasCtxt = fst . ctxtRepr

-- | convert OType to data for instance heads
instrCtx S  = Just ("PVar out",1)
instrCtx SS = Just ("PVar out",2)
instrCtx C  = Just ("KVar out",1)
instrCtx V  = Just ("Varable out",1)
instrCtx MA = Just ("Nat d",1)
instrCtx _  = Nothing

oHasCtxt = isJust . instrCtx

-- | determine the proper outtype for instance decls
monoCtxt A    = "(ASig (S n))"
monoCtxt K    = "(KSig (S n))"
monoCtxt I    = "(INum (S n))"
monoCtxt F    = "(FSig (S n))"
monoCtxt U    = "()"
monoCtxt IK   = "(INum (S n), KSig (S n))"
monoCtxt KI   = "(KSig (S n), INum (S n))"
monoCtxt KK   = "(KSig (S n), KSig (S n))"
monoCtxt FF   = "(FSig (S n), FSig (S n))"
monoCtxt AA   = "(ASig (S n), ASig (S n))"
monoCtxt AK   = "(ASig (S n), KSig (S n))"
monoCtxt AAA  = "(ASig (S n), ASig (S n), ASig (S n))"
monoCtxt KKK  = "(KSig (S n), KSig (S n), KSig (S n))"
monoCtxt AAAA = "(ASig (S n), ASig (S n), ASig (S n), ASig (S n))"
monoCtxt KKKK = "(KSig (S n), KSig (S n), KSig (S n), KSig (S n))"
monoCtxt IIII = "(INum (S n), INum (S n), INum (S n), INum (S n))"
monoCtxt Str  = "(VString (S n))"
monoCtxt MA   = "d"
monoCtxt S    = "out"
monoCtxt SS   = "out"
monoCtxt C    = "out"
monoCtxt V    = "out"