module Ivory.Language.Struct.Parser (
parseStructDefs
, StructDef(..), Field(..), Type(..)
) where
import Control.Applicative ((<$>),(<$),(<*>),(*>),(<*),many,(<|>),some)
import Control.Monad (void)
import Data.Char (isLower,isAscii,isAlphaNum,isLetter)
import Text.Parsec.Char (char,satisfy,spaces,string,digit)
import Text.Parsec.Combinator (sepBy1)
import Text.Parsec.String (Parser)
import Text.Parsec.Prim (try)
data StructDef
= StructDef String [Field]
| AbstractDef String String
| StringDef String Integer
deriving (Show)
data Field = Field
{ fieldName :: String
, fieldType :: Type
} deriving (Show)
data Type
= TApp Type Type
| TCon String
| TNat Integer
| TSym String
deriving (Show)
parseStructDefs :: Parser [StructDef]
parseStructDefs = comments *> some parseStructDef
token' :: Parser a -> Parser a
token' body = try body <* comments
where
comments :: Parser ()
comments = spaces *> void (many comment)
where
comment = string "--" *> many (satisfy (not . newline)) *> spaces
newline = (== '\n')
token :: String -> Parser ()
token str = void (token' (string str))
braces :: Parser a -> Parser a
braces body = token "{" *> body <* token "}"
parens :: Parser a -> Parser a
parens body = token "(" *> body <* token ")"
semi :: Parser ()
semi = token ";"
parseStructDef :: Parser StructDef
parseStructDef = structDef <|> abstractDef <|> stringDef
structDef :: Parser StructDef
structDef = StructDef <$ token "struct"
<*> parseName
<*> braces (parseField `sepBy1` semi)
abstractDef :: Parser StructDef
abstractDef = AbstractDef <$ token "abstract"
<* token "struct"
<*> parseName
<*> parseString
stringDef :: Parser StructDef
stringDef = StringDef <$ token "string"
<*> parseName
<*> number
parseName :: Parser String
parseName = token' ((:) <$> satisfy isLetter <*> following)
parseIdent :: Parser String
parseIdent = token' ((:) <$> satisfy isLower <*> following)
following :: Parser String
following = many (satisfy (\c -> isAscii c && (isAlphaNum c || c == '_')))
parseField :: Parser Field
parseField = Field <$> parseIdent
<* token "::"
<*> parseType
parseType :: Parser Type
parseType = foldl1 TApp <$> some parseAType
parseAType :: Parser Type
parseAType = (TNat <$> number)
<|> (TCon <$> parseName)
<|> (TSym <$> parseString)
<|> parens parseType
number :: Parser Integer
number = read <$> token' (some digit)
parseString :: Parser String
parseString = char '"' *> (many (satisfy (/= '"'))) <* token' (char '"')