packages feed

octopus-0.0.2.0: Language/Octopus/Parser.hs

{-| Parse Octopus source code. The Octopus grammar is:


> file ::= ('export' <expr>)? <stmt>*
> stmt ::= <expr> | _field_ <expr> | 'open' <expr>
> expr ::= <atom> | <list> | <object>
>       |  <combination> | <block> | <quotation>
>       |  <accessor> | <mutator>
>       | '(' <expr> ')' |  /\.<expr>/
> 
> atom ::= _symbol_ | _number_ | _string_ | _heredoc_ | _primitive_
> list ::= '[' (<expr>+ (',' <expr>+)*)? ']'
> object ::= '{' (_field_ <expr>+ (',' _field_ <expr>+)*)? '}'
> combination ::= '(' <expr> <expr>+ ')'
> block ::= 'do' <stmt>+ ';'
> accessor ::= /@<name>/ | /:<name>/
> mutator ::= '@(' _field_ <expr>+ ')' | ':(' _field_ <expr>+ ')'
> quotation ::= /`<expr>/
> 
> symbol ::= _name_ - reserved
>     reserved = {'do', 'letrec', 'export', 'open'}
> primitive ::= /#<[a-zA-Z]+>/
> field ::= /<name>:/
> number ::= /[+-]?(0[xX]<hexnum>|0[oO]<octnum>|0[bB]<binnum>|<decnum>)/
>     decnum ::= /\d+(\.\d+<exponent>?|\/\d+)?/
>     hexnum ::= /\x+(\.\x+([hH][+-]?\x+)?|\/\x+)?/
>     octnum ::= /[0-7]+(\.[0-7]+<exponent>?|\/[0-7]+)?/
>     binnum ::= /[01]+(\.[01]+<exponent>?|\/[01]+)?/
>     exponent ::= /[eE][+-]?\d+|[hH][+-]?\x+/
> string ::= /"([^"\\]|\\[abefntv'"&\\]|\\<numescape>|\\\s*\n\s*\\)*"/
>     numescape ::= /[oO][0-7]{3}|[xX]\x{2}|u\x{4}|U0\x{5}|U10x{4}/
> heredoc ::= /#<<(?'END'\w+)\n.*?\n\g{END}>>(\n|$)/
> name ::= /<namehead><nametail>|-<namehead><nametail>|-(-<nametail>)?/
>     namehead = /[^#\\"`()[]{}@:;.,0-9-]/
>     nametail = /[^#\\"`()[]{}@:;.,]*/
> 
> linecomment ::= /#(?!<)\.*?\n/
> blockcomment ::= /#\{([^#}]+|<blockcomment>|#[^{]|\}[^#])*\}#/

-}
module Language.Octopus.Parser where

import Import

import qualified Data.ByteString.Lazy as BS
import qualified Data.Text as T
import Text.Parsec ( Parsec, SourceName, ParseError
                   , try, (<?>), unexpected
                   , char, anyChar, oneOf, noneOf, eof)
import qualified Text.Parsec as P
import Language.Parse
import Language.Desugar

import Language.Octopus.Data
import Language.Octopus.Data.Shortcut
import Language.Octopus.Basis
import Language.Octopus.Parser.Preprocess


type Parser = Parsec String ()
type Directive = String

parseOctopusExpr :: SourceName -> String -> Either ParseError Val
parseOctopusExpr sourceName input = desugar <$> P.runParser (padded expr <* padded eof) () sourceName input

parseOctopusFile :: SourceName -> String -> Either ParseError ([Directive], Val)
parseOctopusFile sourceName input =
    let (directives, code) = partitionCode input
    in (,) directives <$> P.runParser octopusFile () sourceName code
    where
    octopusFile = do
        es <- P.many $ desugarStatement <$> padded statement
        padded eof
        return $ loop es
    loop [] = mkCall getenv (mkOb [])
    loop (Defn s:rest) = mkCall (mkDefn s) (loop rest)
    loop (Open e:rest) = mkCall (mkOpen e) (loop rest)
    loop (Expr e:rest) = mkCall (mkExpr e) (loop rest)
    getenv = (mkCall (Pr Vau) (mkSq [mkSq [mkSy "e", mkOb []], mkSy "e"]))

define :: Parser (Defn Syx)
define = do
    var <- try (expr <* char ':' <* whitespace)
    body <- expr
    return (var, body)

letrec :: Parser (Defn Syx)
letrec = do
    try $ string "letrec" <* whitespace
    var <- try (expr <* char ':' <* whitespace)
    body <- expr
    return (var, body)    

open :: Parser Syx
open = do
    try $ string "open" *> whitespace
    expr

expr :: Parser Syx
expr = composite P.<|> atom
    where
    atom = Lit <$> P.choice [symbol, numberLit, textLit, heredoc, primitive] <?> "atom"
    composite = P.choice [ block, combine, sq, ob, quote, dottedExpr
                         , accessor, mutator, infixAccessor, infixMutator]

statement :: Parser (Statement Syx)
statement = P.choice [ Defn <$> define
                     , LRec <$> letrec
                     , Open <$> open
                     , Expr <$> expr
                     ]


------ Sugar ------
data Statement a = Defn (Defn a)
                 | LRec (Defn a)
                 | Expr a
                 | Open a
                 | Deco a
    deriving (Show)
type Defn a = (a, a)
data Syx = Lit Val
         | Call [Syx]
         | SqSyx [Syx]
         | ObExpr [(Symbol, Syx)]
         | Do [Statement Syx]
         | Infix Syx
    deriving (Show)

desugar :: Syx -> Val
desugar (Lit x) = x
desugar (Call [x]) = desugar x
desugar (Call xs) = loop . (desugar <$>) $ revTripBy isInfix (id, rewrite) xs
    where
    rewrite [] inf rest = error "TODO syntax error: infix :/. needs a subject"
    rewrite subject (Infix inf) rest = inf : Call subject : rest
    loop [e] = e
    loop [f, x] = mkCall f x
    loop es = mkCall (loop $ init es) (last es)
    isInfix (Infix _) = True
    isInfix _ = False
desugar (SqSyx xs) = mkSq $ desugar <$> xs
desugar (ObExpr xs) = mkOb $ desugarField <$> xs
desugar (Do xs) = loop xs
    where
    loop [Defn d]      = mkCall (mkDefn $ desugarDefine d) (mkOb [])
    loop [Expr e]      = desugar e
    loop (Defn d:rest) = mkCall (mkDefn $ desugarDefine d) (loop rest)
    loop (LRec d:rest) = mkCall (mkDefn $ desugarLetrec d) (loop rest)
    loop (Open e:rest) = mkCall (mkOpen $ desugar e) (loop rest)
    loop (Expr e:rest) = mkCall (mkExpr $ desugar e) (loop rest)
desugar x = error $ "INTERNAL ERROR Octopus.Parser.desugar: " ++ show x

desugarField :: (Symbol, Syx) -> (Symbol, Val)
desugarField (k, e) = (k, desugar e)

desugarDefine :: Defn Syx -> (Val, Val)
desugarDefine (x, e) = (desugar x, desugar e)

desugarLetrec :: Defn Syx -> (Val, Val)
desugarLetrec (x, e) = let f = desugar x
                       in (f, mkCall (mkSy "__Y__") (mkCall (mkCall (mkSy "__lambda__") f) (desugar e)))

desugarStatement :: Statement Syx -> Statement Val
desugarStatement (Defn d) = Defn (desugarDefine d)
desugarStatement (LRec d) = Defn (desugarLetrec d)
desugarStatement (Expr e) = Expr (desugar e)
desugarStatement (Open e) = Open (desugar e)
desugarStatement (Deco f) = Deco (desugar f)


------ Atoms ------
primitive :: Parser Val
primitive = P.choice (map mkPrimParser table)
    where
    mkPrimParser (name, val) = string ("#<" ++ name ++ ">") >> return val
    table = [ ("vau", Pr Vau), ("eval", Pr Eval), ("match", Pr Match), ("ifz!", Pr Ifz), ("import", Pr Imp)
            , ("eq", Pr Eq), ("neq", Pr Neq), ("lt", Pr Lt), ("lte", Pr Lte), ("gt", Pr Gt), ("gte", Pr Gte)
            , ("add", Pr Add) , ("mul", Pr Mul) , ("sub", Pr Sub) , ("div", Pr Div)
            , ("numer", Pr Numer) , ("denom", Pr Denom) , ("numParts", Pr NumParts)
            , ("openFile", Pr OpenFp), ("flush", Pr FlushFp), ("close", Pr CloseFp)
            , ("readByte", Pr ReadFp), ("writeByte", Pr WriteFp)
            , ("mkTag", Pr MkTag)
            , ("len", Pr Len) , ("cat", Pr Cat) , ("cut", Pr Cut)
            , ("extends", Pr Extends) , ("del", Pr Delete) , ("keys", Pr Keys) , ("get", Pr Get)
            , ("handle", Pr Handle) , ("raise", Pr Raise)

            , ("stdin", fpStdin), ("stdout", fpStdout), ("stdin", fpStderr)

            , ("TypeError", exnTypeError), ("MatchFail", exnMatchFail)
            , ("ScopeError", exnScopeError), ("AttrError", exnAttrError), ("IndexError", exnIndexError)
            , ("DivZero", exnDivZero), ("IOError", exnIOError)
            , ("SyntaxError", exnSyntaxError), ("ImportError", exnImportError)
            ]

symbol :: Parser Val
symbol = do
    n <- name
    when (n `elem` ["do", "letrec", "export", "open"])
        (unexpected $ "reserved word (" ++ n ++ ")") --FIXME report error position before token, not after
    return $ mkSy n

numberLit :: Parser Val
numberLit = Nm <$> anyNumber

--TODO maybe bytes literals

textLit :: Parser Val
textLit = do
    content <- catMaybes <$> between2 (char '\"') (P.many maybeLiteralChar)
    return $ mkTx content

heredoc :: Parser Val
heredoc = do
    string "#<<"
    end <- P.many1 P.letter <* char '\n'
    let endParser = char '\n' *> P.string (end ++ ">>") <* (void (char '\n') P.<|> eof)
    mkTx <$> anyChar `manyThru` endParser


------ Composites ------
combine :: Parser Syx
combine = do
    postPadded $ char '('
    e <- bareCombination
    padded $ char ')'
    return e

sq :: Parser Syx
sq = do
    postPadded $ char '['
    elems <- bareCombination `P.sepBy` padded comma
    padded $ char ']'
    return $ SqSyx elems

ob :: Parser Syx
ob = do
    postPadded $ char '{'
    elems <- pair `P.sepBy` padded comma
    padded $ char '}'
    return $ ObExpr elems
    where
    pair = do
        key <- intern <$> padded name
        char ':' <* whitespace
        val <- bareCombination
        return (key, val)

block :: Parser Syx
block = do
        try $ string "do" >> whitespace
        states <- P.many1 $ postPadded statement
        char ';'
        return $ Do states

quote :: Parser Syx
quote = do
    char '`'
    e <- expr
    return $ Call [Lit $ mkSy "__quote__", e]

dottedExpr :: Parser Syx
dottedExpr = Infix <$> (char '.' *> expr)

accessor :: Parser Syx
accessor = do
    key <- try $ char '@' *> name
    return $ Call [Lit $ mkSy "__get__", Lit $ mkSy key]

mutator :: Parser Syx
mutator = do
    string "@("
    key <- name <* char ':' <* whitespace
    e <- bareCombination
    char ')'
    return $ Call [Lit $ mkSy "__modify__", Lit $ mkSy key, e]

infixAccessor :: Parser Syx
infixAccessor = do
    key <- try $ char ':' *> name
    return . Infix $ Call [Lit $ mkSy "__get__", Lit $ mkSy key]

infixMutator :: Parser Syx
infixMutator = do
    string ":("
    key <- name <* char ':' <* whitespace
    e <- bareCombination 
    char ')'
    return . Infix $ Call [Lit $ mkSy "__modify__", Lit $ mkSy key, e]


------ Space ------
whitespace :: Parser ()
whitespace = (<?> "space") . P.skipMany1 $ P.choice [spaces1, lineComment, blockComment]

lineComment :: Parser ()
lineComment = void $ do
    try $ char '#' >> P.notFollowedBy (oneOf "{<")
    anyChar `manyThru` (void (char '\n') P.<|> eof)

blockComment :: Parser ()
blockComment = P.between (string "#{") (string "}#") $ P.skipMany $
    P.choice [ void $ P.many1 (noneOf "}#")
             , blockComment
             , void $ try $ char '#' >> P.notFollowedBy (char '{')
             , void $ try $ char '}' >> P.notFollowedBy (char '#')
             ]

padded :: Parser a -> Parser a
padded p = try $ P.optional whitespace >> p
postPadded :: Parser a -> Parser a
postPadded p = p <* P.optional whitespace


------ Helpers ------
name :: Parser String
name = P.choice [ (:) <$> namehead <*> nametail
                , (:) <$> char '-' <*> P.option [] ((:) <$> (namehead P.<|> char '-') <*> nametail)
                ]
    where
    namehead = blacklistChar (`elem` reservedFirstChar)
    nametail = P.many $ blacklistChar (`elem` reservedChar)
    reservedChar = "#\\\"`()[]{}@:;.,"
    reservedFirstChar = reservedChar ++ "-0123456789"

comma :: Parser ()
comma = char ',' >> whitespace

bareCombination :: Parser Syx
bareCombination = do
    es <- P.many1 (postPadded expr)
    return $ case es of { [e] -> e; es -> Call es }

mkDefn (x, val) = mkCall (mkCall (mkSy "__let__") x) val
mkOpen env = mkCall (mkSy "__open__") env
mkExpr e = mkCall (mkCall (mkSy "__let__") (mkOb [])) e