packages feed

casui-0.3: Casui/Parse.hs

{-# LANGUAGE ScopedTypeVariables #-}

module Casui.Parse where

import Casui.Name
import Casui.Utils
import Casui.Value
import Casui.Module

import Data.Ratio
import Data.List
import Control.Monad
import Control.Monad.Error
import Control.Applicative hiding ((<|>), many)
import Control.Arrow
import Text.ParserCombinators.Parsec


nonSymbolChars :: [Char]
nonSymbolChars = " \r\n\t()\"#',`"

parseValue :: Parser ParsedValue
parseValue = pSpacesOrComment >> (
      pQQU
  <|> pList
  <|> pString
  <|> pNumber
  <|> pSymbol
  ) <* pSpacesOrComment

pSpacesOrComment :: Parser ()
pSpacesOrComment = spaces >> ((try $ char ';' >> skipMany (noneOf "\n")) <|> return ())

pQQU :: Parser ParsedValue
pQQU = do c <- oneOf "'`,"
          let n = case c of '\'' -> "quote"; ',' -> "unquote"; '`' -> "quasiquote"; _ -> unreachable
          spaces
          v <- parseValue
          ParsedValue <$> getPosition <*> return (VStruct (builtin $ "script:" ++ n) [v])

pList :: Parser ParsedValue
pList = ParsedValue <$> getPosition <*> (VList <$> (pParens $ many (parseValue <* spaces)))

pParens :: Parser a -> Parser a
pParens p = char '(' >> p <* char ')'

pString :: Parser ParsedValue
pString = ParsedValue <$> getPosition <*> fmap VString (
           char '"'
           >> (many $ pStringEscape <|> noneOf ['"'])
           <* char '"' )

pStringEscape :: Parser Char
pStringEscape = char '\\' >> do
                   c <- anyChar
                   case lookup c [('n','\n'),('\\','\\'),('\r','\r'),('\t','\t')] of
                     Just v -> return v
                     _ -> unexpected "unvalid use of backslash in string"

pNumber :: Parser ParsedValue
pNumber = lookAhead (digit <|> char '.') >> do
  (i :: Integer) <- maybe 0 read <$> optionMaybe (many1 digit)
  f <- maybe 0 fpart <$> optionMaybe (char '.' >> many1 digit)
  ParsedValue <$> getPosition <*> (return . VExact . toRational $ toRational i + f)
  where fpart = liftM2 (%) read $ (10^) . length
             
pSymbol :: Parser ParsedValue
pSymbol = ParsedValue <$> getPosition <*> (VName <$> pName)

pName :: Parser Name
pName = mkName <$> many1 (noneOf nonSymbolChars)


instance Show ParsedValue where
  show (ParsedValue _ v) = show v

uvModule :: FileId -> Name -> ParsedValue ->
            Either (TagError SourcePos) (Module ParsedName ParsedValue)
uvModule extid (Name parent)
  (val -> VList ((val -> VName (Name ["module"])) : (val -> VName (Name [nam])) : content)) = 
      let ([imps, defs], props) = groupVL [mkName "import", mkName "define"] $ content in do
        imports <- fmap (if null parent then id else (ParsedName Nothing (Name parent):)) .
                   fmap concat . mapM listImports $ map snd imps
        (defines, dprops) <- second concat . unzip <$> mapM (uncurry listDefines) defs
        properties <- map mkProp . (dprops ++) <$> mapM getProp props
        return $ Module (FullName extid $ parent ++ [nam])
          imports defines properties
        where
          listImports :: [ParsedValue] -> Either (TagError SourcePos) [ParsedName]
          listImports syms = mapM getName syms
          listDefines _def (sym:pprops) = do
            n <- getName sym
            s <- case n of ParsedName _ (Name [s]) -> return s
                           _otherwise -> Left $ err "compound name in define" $ pvSourcePos sym
            (mv, pprops') <- extractValue pprops
            ps <- mapM getPProp pprops'
            return ((s, mv), map (\(a,b) -> (a, n, b)) ps) 
          listDefines def _ = Left $ err "empty define" $ pvSourcePos def
          extractValue ps = case partition notList ps of
            ([a], l) -> return (Just a, l)
            ([], l) -> return (Nothing, l)
            ((_:v:_), _) -> Left $ err "too many values in define" $ pvSourcePos v
          notList (val -> VList _) = True
          notList _ = False
          getPProp :: ParsedValue -> Either (TagError SourcePos) (ParsedName, [ParsedValue])
          getPProp (val -> VList (prop:arg)) = liftM2 (,) (getName prop) (return arg)
          getPProp v = Left $ err "expecting a partial property" $ pvSourcePos v
          getProp :: ParsedValue -> Either (TagError SourcePos) (ParsedName, ParsedName, [ParsedValue])
          getProp (val -> VList (prop:name:arg)) = liftM3 (,,) (getName prop) (getName name) (return arg)
          getProp v = Left $ err "expecting a property" $ pvSourcePos v
          getName :: ParsedValue -> Either (TagError SourcePos) ParsedName
          getName v@(val -> VName n) = return $ ParsedName (Just $ pvSourcePos v) (n)
          getName v = Left $ err "expecting a symbol" $ pvSourcePos v
          mkProp :: (ParsedName, ParsedName, [ParsedValue]) ->
                    (ParsedName, Property ParsedName ParsedValue)
          mkProp (prop,obj,arg) = (obj, E prop arg)

uvModule _ _ (ParsedValue sp _) = Left $ err "expecting a module declaration" sp

groupVL :: Val v => [Name] -> [v] -> ([[(v, [v])]], [v])
groupVL [] vl = ([], vl)
groupVL (name:names) vl = first (match:) $ groupVL names others
  where (match, others) = extractVL name vl

extractVL :: Val v => Name -> [v] -> ([(v, [v])], [v])
extractVL _ [] = ([],[])
extractVL name (v:vs) = put $ extractVL name vs
  where put = case val v of VList ((val -> VName head):tail)
                                | head == name -> first ((v, tail):)
                            _otherwise -> second (v:)


type ParsedModule = Module ParsedName ParsedValue

data ParsedName = ParsedName (Maybe SourcePos) Name

instance HasName ParsedName where
  nameOf (ParsedName _ name) = name

data ParsedValue = ParsedValue { 
  pvSourcePos :: SourcePos,
  pvValue :: Value ParsedValue
  }

instance Val ParsedValue where
  val (ParsedValue _ v) = v