packages feed

noise-0.0.1: src/Text/Noise/Parser.hs

module Text.Noise.Parser
( parse
, ParseError
) where

import Control.Applicative
import Text.ParserCombinators.Parsec (ParseError, sepBy1, eof, option)
import Text.Parsec.Prim (try, (<?>))
import Text.Parsec.Pos (initialPos)
import qualified Text.Parsec.Expr as Expr
import qualified Text.Parsec.Prim (runParser)
import Text.Noise.Parser.Token (Parser, ranged)
import qualified Text.Noise.Parser.Token as Token
import qualified Text.Noise.Parser.AST as AST

parse :: String -> String -> Either ParseError AST.SourceFile
parse = Text.Parsec.Prim.runParser sourceFile (initialPos "" )

qualifiedIdentifier :: Parser AST.QualifiedIdentifier
qualifiedIdentifier = ranged
  (AST.QualifiedIdentifier <$> sepBy1 Token.identifier Token.dot)

floatLiteral :: Parser AST.Expression
floatLiteral = ranged (AST.FloatLiteral <$> Token.number)

colorLiteral :: Parser AST.Expression
colorLiteral = ranged (AST.ColorLiteral <$> Token.colorLiteral)

stringLiteral :: Parser AST.Expression
stringLiteral = ranged (AST.StringLiteral <$> Token.stringLiteral)

functionCall :: Parser AST.Expression
functionCall = ranged $ AST.FunctionCall
  <$> qualifiedIdentifier
  <*> option [] (Token.parens (Token.commaSeparated argument))
  <*> option Nothing (Just <$> block)

block :: Parser AST.Block
block = ranged $ AST.Block
  <$> reserved "with"
  <*> many statement
  <*> reserved "end"

operator :: AST.OperatorFunction -> Parser AST.Operator
operator op = ranged $ do
  Token.reservedOp str
  return (AST.Operator op)
  where str = case op of
          AST.Add -> "+"
          AST.Sub -> "-"
          AST.Mul -> "*"
          AST.Div -> "/"

expression :: Parser AST.Expression
expression = Expr.buildExpressionParser opTable term
  where opTable = [ [prefix AST.Sub]
                  , [infixLeft AST.Mul, infixLeft AST.Div]
                  , [infixLeft AST.Add, infixLeft AST.Sub] ]
        infixLeft op = Expr.Infix (AST.InfixOperation <$> operator op) Expr.AssocLeft
        prefix op = Expr.Prefix (AST.PrefixOperation <$> operator op)
        term = colorLiteral
           <|> floatLiteral
           <|> stringLiteral
           <|> functionCall
           <|> Token.parens expression
           <?> "expression"

expressionStatement :: Parser AST.Statement
expressionStatement = AST.ExpressionStatement <$> expression

argumentPrototype :: Parser AST.ArgumentPrototype
argumentPrototype = ranged (AST.RequiredArgumentPrototype <$> Token.identifier)

functionPrototype :: Parser AST.FunctionPrototype
functionPrototype = ranged $ AST.FunctionPrototype
  <$> qualifiedIdentifier
  <*> option [] (Token.parens (Token.commaSeparated argumentPrototype))

reserved :: String -> Parser AST.Reserved
reserved str = ranged $ Token.reserved str >> return (AST.Reserved str)

functionDefStatement :: Parser AST.Statement
functionDefStatement = ranged $ AST.DefinitionStatement
  <$> reserved "let"
  <*> functionPrototype <* Token.symbol "="
  <*> expression

statement :: Parser AST.Statement
statement = functionDefStatement
        <|> expressionStatement
        <?> "statement"

keywordArgument :: Parser AST.Argument
keywordArgument = ranged $ AST.KeywordArgument
  <$> try (Token.identifier <* Token.symbol ":")
  <*> expression

positionalArgument :: Parser AST.Argument
positionalArgument = AST.PositionalArgument <$> expression

argument :: Parser AST.Argument
argument = keywordArgument
       <|> positionalArgument
       <?> "argument"

sourceFile :: Parser AST.SourceFile
sourceFile = do
  Token.whiteSpace
  ast <- ranged (AST.SourceFile <$> many statement)
  eof
  return ast